Start of work on more user-friendly account valuation.

This commit is contained in:
Geoff Taylor 2021-11-01 16:16:54 +00:00
parent f27358efbb
commit b06bf591da
3 changed files with 248 additions and 0 deletions

39
bin/show-account-valuation Executable file
View File

@ -0,0 +1,39 @@
#!/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)
print(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

@ -79,6 +79,7 @@ from .tokenlookup import TokenLookup, NullTokenLookup, CompoundTokenLookup
from .tokenvalue import TokenValue from .tokenvalue import TokenValue
from .tradeexecutor import TradeExecutor, NullTradeExecutor, ImmediateTradeExecutor from .tradeexecutor import TradeExecutor, NullTradeExecutor, ImmediateTradeExecutor
from .transactionscout import TransactionScout, fetch_all_recent_transaction_signatures, mango_instruction_from_response from .transactionscout import TransactionScout, fetch_all_recent_transaction_signatures, mango_instruction_from_response
from .valuation import TokenValuation, AccountValuation, Valuation
from .version import Version from .version import Version
from .wallet import Wallet from .wallet import Wallet
from .walletbalancer import TargetBalance, FixedTargetBalance, PercentageTargetBalance, parse_target_balance, parse_fixed_target_balance, sort_changes_for_trades, calculate_required_balance_changes, FilterSmallChanges, WalletBalancer, NullWalletBalancer, LiveWalletBalancer, LiveAccountBalancer from .walletbalancer import TargetBalance, FixedTargetBalance, PercentageTargetBalance, parse_target_balance, parse_fixed_target_balance, sort_changes_for_trades, calculate_required_balance_changes, FilterSmallChanges, WalletBalancer, NullWalletBalancer, LiveWalletBalancer, LiveAccountBalancer

208
mango/valuation.py Normal file
View File

@ -0,0 +1,208 @@
# # ⚠ 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 .accounttokenvalues import AccountTokenValues
from .cache import Cache
from .context import Context
from .group import Group
from .openorders import OpenOrders
from .token import Token, SolToken
from .tokenvalue import TokenValue
class TokenValuation:
def __init__(self, raw_token_value: TokenValue, price_token_value: TokenValue, value_token_value: TokenValue):
self.raw: TokenValue = raw_token_value
self.price: TokenValue = price_token_value
self.value: TokenValue = 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.token_lookup.find_by_symbol(symbol)
if token is None:
raise Exception(f"Could not find token for symbol: {symbol}")
currency_token = context.token_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: TokenValue = TokenValue(token, balance)
price_token_value: TokenValue = TokenValue(currency_token, price)
value_token_value: TokenValue = TokenValue(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: TokenValue) -> "TokenValuation":
token_to_lookup: Token = token_balance.token
if token_balance.token == SolToken:
token_to_lookup = context.token_lookup.find_by_symbol_or_raise("SOL")
cached_token_price: TokenValue = group.token_price_from_cache(cache, token_to_lookup)
balance_value: TokenValue = 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 = group.fetch_balances(context, address)
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]):
self.name: str = name
self.address: PublicKey = address
self.tokens: typing.Sequence[TokenValuation] = tokens
@property
def value(self) -> TokenValue:
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.basket:
if (asset.net_value.value != 0) or ((asset.perp_account is not None) and not asset.perp_account.empty):
report: AccountTokenValues = AccountTokenValues.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_token.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: TokenValue = 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) -> TokenValue:
wallet_tokens_value: TokenValue = 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: TokenValue = self.value
wallet_value: TokenValue = 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: TokenValue = 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"""« 𝚅𝚊𝚕𝚞𝚊𝚝𝚒𝚘𝚗 of {address:<47} {self.value}
Wallet Tokens (total: {wallet_total}):
{wallet_tokens}
{accounts_tokens}
»"""