def balance(self, prices: typing.List[TokenValue]): padding = "\n " def balances_report(balances) -> str: return padding.join(list([f"{bal}" for bal in balances])) current_balances = self._fetch_balances() total_value = Decimal(0) for bal in current_balances: price = TokenValue.find_by_token(prices, bal.token) value = bal.value * price.value total_value += value self.logger.info(f"Starting balances: {padding}{balances_report(current_balances)} - total: {total_value}") resolved_targets: typing.List[TokenValue] = [] for target in self.target_balances: price = TokenValue.find_by_token(prices, target.token) resolved_targets += [target.resolve(price.value, total_value)] balance_changes = calculate_required_balance_changes(current_balances, resolved_targets) self.logger.info(f"Full balance changes: {padding}{balances_report(balance_changes)}") dont_bother = FilterSmallChanges(self.action_threshold, current_balances, prices) filtered_changes = list(filter(dont_bother.allow, balance_changes)) self.logger.info(f"Filtered balance changes: {padding}{balances_report(filtered_changes)}") if len(filtered_changes) == 0: self.logger.info("No balance changes to make.") return sorted_changes = sort_changes_for_trades(filtered_changes) self._make_changes(sorted_changes) updated_balances = self._fetch_balances() self.logger.info(f"Finishing balances: {padding}{balances_report(updated_balances)}")
def calculate_required_balance_changes(current_balances: typing.List[TokenValue], desired_balances: typing.List[TokenValue]) -> typing.List[TokenValue]: changes: typing.List[TokenValue] = [] for desired in desired_balances: current = TokenValue.find_by_token(current_balances, desired.token) change = TokenValue(desired.token, desired.value - current.value) changes += [change] return changes
def _fetch_balances(self) -> typing.List[TokenValue]: balances: typing.List[TokenValue] = [] for token in self.tokens: balance = TokenValue.fetch_total_value(self.context, self.wallet.address, token) balances += [balance] return balances
def __init__(self, action_threshold: Decimal, balances: typing.List[TokenValue], prices: typing.List[TokenValue]): self.logger: logging.Logger = logging.getLogger(self.__class__.__name__) self.prices: typing.Dict[str, TokenValue] = {} total = Decimal(0) for balance in balances: price = TokenValue.find_by_token(prices, balance.token) self.prices[f"{price.token.mint}"] = price total += price.value * balance.value self.total_balance = total self.action_threshold_value = total * action_threshold self.logger.info(f"Wallet total balance of {total} gives action threshold value of {self.action_threshold_value}")
def resolve(self, current_price: Decimal, total_value: Decimal) -> TokenValue: target_value = total_value * self.target_fraction target_size = target_value / current_price return TokenValue(self.token, target_size)
def resolve(self, current_price: Decimal, total_value: Decimal) -> TokenValue: return TokenValue(self.token, self.value)
btc_target = parser.parse("btc:0.05") prices = [Decimal("60000"), Decimal("4000"), Decimal("1")] # Ordered as per Group index ordering desired_balances = [] for target in [eth_target, btc_target]: token_index = group.price_index_of_token(target.token) price = prices[token_index] resolved = target.resolve(price, Decimal(10000)) desired_balances += [resolved] assert(desired_balances[0].token.name == "ETH") assert(desired_balances[0].value == Decimal("0.5")) assert(desired_balances[1].token.name == "BTC") assert(desired_balances[1].value == Decimal("0.05")) current_balances = [ TokenValue(eth, Decimal("0.6")), # Worth $2,400 at the test prices TokenValue(btc, Decimal("0.01")), # Worth $6,00 at the test prices TokenValue(usdt, Decimal("7000")), # Remainder of $10,000 minus above token values ] changes = calculate_required_balance_changes(current_balances, desired_balances) for change in changes: order_type = "BUY" if change.value > 0 else "SELL" print(f"{change.token.name} {order_type} {change.value}") # To get from our current balances of 0.6 ETH and 0.01 BTC to our desired balances of # 0.5 ETH and 0.05 BTC, we need to sell 0.1 ETH and buy 0.04 BTC. But we want to do the sell # first, to make sure we have the proper liquidity when it comes to buying. sorted_changes = sort_changes_for_trades(changes) assert(sorted_changes[0].token.name == "ETH") assert(sorted_changes[0].value == Decimal("-0.1"))
if MARGIN_ACCOUNT_TO_LIQUIDATE == "": raise Exception( "No margin account to liquidate - try setting the variable MARGIN_ACCOUNT_TO_LIQUIDATE to a margin account public key." ) from Context import default_context from Wallet import default_wallet if default_wallet is None: print("No default wallet file available.") else: print("Wallet Balances Before:") group = Group.load(default_context) balances_before = group.fetch_balances(default_wallet.address) TokenValue.report(print, balances_before) prices = group.fetch_token_prices() margin_account = MarginAccount.load( default_context, PublicKey(MARGIN_ACCOUNT_TO_LIQUIDATE), group) intrinsic_balance_sheets_before = margin_account.get_intrinsic_balance_sheets( group) print("Margin Account Before:", intrinsic_balance_sheets_before) liquidator = ForceCancelOrdersAccountLiquidator( default_context, default_wallet) transaction_id = liquidator.liquidate(group, margin_account, prices) if transaction_id is None: print("No transaction sent.") else: print("Transaction ID:", transaction_id) print("Waiting for confirmation...")