Added 'dry run' option to commands that perform transactions.
This commit is contained in:
parent
3db09e922c
commit
fab0d909c2
|
@ -2,7 +2,7 @@
|
|||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "nominated-blind",
|
||||
"id": "scenic-confidence",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# ⚠ Warning\n",
|
||||
|
@ -16,18 +16,22 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "coupled-butterfly",
|
||||
"id": "loved-undergraduate",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# 🥭 AccountLiquidator\n",
|
||||
"\n",
|
||||
"An `AccountLiquidator` takes a `MarginAccount` and liquidates it (if possible)."
|
||||
"An `AccountLiquidator` liquidates a `MarginAccount`, if possible.\n",
|
||||
"\n",
|
||||
"The follows the common pattern of having an abstract base class that defines the interface external code should use, along with a 'null' implementation and at least one full implementation.\n",
|
||||
"\n",
|
||||
"The idea is that preparing code can choose whether to use the null implementation (in the case of a 'dry run' for instance) or the full implementation, but the code that defines the algorithm - which actually calls the `AccountLiquidator` - doesn't have to care about this choice."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "acceptable-porter",
|
||||
"id": "hearing-reset",
|
||||
"metadata": {
|
||||
"jupyter": {
|
||||
"source_hidden": true
|
||||
|
@ -35,6 +39,7 @@
|
|||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import abc\n",
|
||||
"import logging\n",
|
||||
"import typing\n",
|
||||
"\n",
|
||||
|
@ -48,40 +53,92 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "scientific-origin",
|
||||
"id": "united-companion",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# 💧 AccountLiquidator class\n",
|
||||
"## 💧 AccountLiquidator class\n",
|
||||
"\n",
|
||||
"This base class takes a `MarginAccount` and liquidates it.\n",
|
||||
"\n",
|
||||
"Derived classes may override `prepare_instructions()` to extend the liquidation process (for example to cancel outstanding orders before liquidating)."
|
||||
"This abstract base class defines the interface to account liquidators, which in this case is just the `liquidate()` method.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "remarkable-discretion",
|
||||
"id": "passive-terrorism",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class AccountLiquidator:\n",
|
||||
"class AccountLiquidator(metaclass=abc.ABCMeta):\n",
|
||||
" def __init__(self):\n",
|
||||
" self.logger: logging.Logger = logging.getLogger(self.__class__.__name__)\n",
|
||||
"\n",
|
||||
" @abc.abstractmethod\n",
|
||||
" def liquidate(self, margin_account: MarginAccount, prices: typing.List[TokenValue]) -> typing.Optional[str]:\n",
|
||||
" raise NotImplementedError(\"AccountLiquidator.liquidate() is not implemented on the base type.\")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "short-compiler",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 🌬️ NullAccountLiquidator class\n",
|
||||
"\n",
|
||||
"A 'null', 'no-op', 'dry run' implementation of the `AccountLiquidator` class."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "wireless-tobacco",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class NullAccountLiquidator(AccountLiquidator):\n",
|
||||
" def __init__(self, context: Context, wallet: Wallet, group: Group):\n",
|
||||
" super().__init__()\n",
|
||||
"\n",
|
||||
" def liquidate(self, margin_account: MarginAccount, prices: typing.List[TokenValue]) -> typing.Optional[str]:\n",
|
||||
" self.logger.info(f\"Skipping liquidation of margin account [{margin_account.address}]\")\n",
|
||||
" return None\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "danish-recycling",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 💧 ActualAccountLiquidator class\n",
|
||||
"\n",
|
||||
"This full implementation takes a `MarginAccount` and liquidates it.\n",
|
||||
"\n",
|
||||
"It can also serve as a base class for further derivation. Derived classes may override `prepare_instructions()` to extend the liquidation process (for example to cancel outstanding orders before liquidating)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "orange-cocktail",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class ActualAccountLiquidator:\n",
|
||||
" def __init__(self, context: Context, wallet: Wallet):\n",
|
||||
" super().__init__()\n",
|
||||
" self.logger: logging.Logger = logging.getLogger(self.__class__.__name__)\n",
|
||||
" self.context = context\n",
|
||||
" self.wallet = wallet\n",
|
||||
" self.group = group\n",
|
||||
"\n",
|
||||
" def prepare_instructions(self, margin_account: MarginAccount, prices: typing.List[TokenValue]) -> typing.List[InstructionBuilder]:\n",
|
||||
" def prepare_instructions(self, group: Group, margin_account: MarginAccount, prices: typing.List[TokenValue]) -> typing.List[InstructionBuilder]:\n",
|
||||
" liquidate_instructions: typing.List[InstructionBuilder] = []\n",
|
||||
" liquidate_instruction = LiquidateInstructionBuilder.from_margin_account_and_market(self.context, self.group, self.wallet, margin_account, prices)\n",
|
||||
" liquidate_instruction = LiquidateInstructionBuilder.from_margin_account_and_market(self.context, group, self.wallet, margin_account, prices)\n",
|
||||
" if liquidate_instruction is not None:\n",
|
||||
" liquidate_instructions += [liquidate_instruction]\n",
|
||||
"\n",
|
||||
" return liquidate_instructions\n",
|
||||
"\n",
|
||||
" def liquidate(self, margin_account: MarginAccount, prices: typing.List[TokenValue]) -> typing.Optional[str]:\n",
|
||||
" instructions = self.prepare_instructions(margin_account, prices)\n",
|
||||
" def liquidate(self, group: Group, margin_account: MarginAccount, prices: typing.List[TokenValue]) -> typing.Optional[str]:\n",
|
||||
" instructions = self.prepare_instructions(group, margin_account, prices)\n",
|
||||
"\n",
|
||||
" if len(instructions) == 0:\n",
|
||||
" return None\n",
|
||||
|
@ -99,13 +156,14 @@
|
|||
" self.logger.debug(\" Program ID:\", instruction.program_id)\n",
|
||||
"\n",
|
||||
" transaction_response = self.context.client.send_transaction(transaction, self.wallet.account)\n",
|
||||
" return transaction_response[\"result\"]\n",
|
||||
" transaction_id = self.context.unwrap_transaction_id_or_raise_exception(transaction_response)\n",
|
||||
" return transaction_id\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "otherwise-framework",
|
||||
"id": "objective-lindsay",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# 🌪️ ForceCancelOrdersAccountLiquidator class\n",
|
||||
|
@ -122,33 +180,33 @@
|
|||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "arctic-issue",
|
||||
"id": "legislative-forty",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class ForceCancelOrdersAccountLiquidator(AccountLiquidator):\n",
|
||||
" def __init__(self, context: Context, wallet: Wallet, group: Group):\n",
|
||||
" super().__init__(context, wallet, group)\n",
|
||||
"class ForceCancelOrdersAccountLiquidator(ActualAccountLiquidator):\n",
|
||||
" def __init__(self, context: Context, wallet: Wallet):\n",
|
||||
" super().__init__(context, wallet)\n",
|
||||
"\n",
|
||||
" def prepare_instructions(self, margin_account: MarginAccount, prices: typing.List[TokenValue]) -> typing.List[InstructionBuilder]:\n",
|
||||
" def prepare_instructions(self, group: Group, margin_account: MarginAccount, prices: typing.List[TokenValue]) -> typing.List[InstructionBuilder]:\n",
|
||||
" force_cancel_orders_instructions: typing.List[InstructionBuilder] = []\n",
|
||||
" for index, market_metadata in enumerate(self.group.markets):\n",
|
||||
" for index, market_metadata in enumerate(group.markets):\n",
|
||||
" open_orders = margin_account.open_orders_accounts[index]\n",
|
||||
" if open_orders is not None:\n",
|
||||
" market = market_metadata.fetch_market(self.context)\n",
|
||||
" orders = market.load_orders_for_owner(margin_account.owner)\n",
|
||||
" order_count = len(orders)\n",
|
||||
" if order_count > 0:\n",
|
||||
" force_cancel_orders_instructions += ForceCancelOrdersInstructionBuilder.multiple_instructions_from_margin_account_and_market(self.context, self.group, self.wallet, margin_account, market_metadata, order_count)\n",
|
||||
" force_cancel_orders_instructions += ForceCancelOrdersInstructionBuilder.multiple_instructions_from_margin_account_and_market(self.context, group, self.wallet, margin_account, market_metadata, order_count)\n",
|
||||
"\n",
|
||||
" all_instructions = force_cancel_orders_instructions + super().prepare_instructions(margin_account, prices)\n",
|
||||
" all_instructions = force_cancel_orders_instructions + super().prepare_instructions(group, margin_account, prices)\n",
|
||||
"\n",
|
||||
" return all_instructions\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "biblical-survey",
|
||||
"id": "funny-ranking",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# 🏃 Running"
|
||||
|
@ -157,7 +215,7 @@
|
|||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "moved-penny",
|
||||
"id": "waiting-general",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
|
@ -173,13 +231,12 @@
|
|||
" group = Group.load(default_context)\n",
|
||||
" prices = group.fetch_token_prices()\n",
|
||||
" margin_accounts = MarginAccount.load_all_for_owner(default_context, default_wallet.address, group)\n",
|
||||
" margin_account = margin_accounts[0]\n",
|
||||
" for margin_account in margin_accounts:\n",
|
||||
" account_liquidator = ActualAccountLiquidator(default_context, default_wallet)\n",
|
||||
" print(account_liquidator.prepare_instructions(group, margin_account, prices))\n",
|
||||
"\n",
|
||||
" account_liquidator = AccountLiquidator(default_context, default_wallet, group)\n",
|
||||
" print(account_liquidator.prepare_instructions(margin_account, prices))\n",
|
||||
"\n",
|
||||
" force_cancel_orders_account_liquidator = ForceCancelOrdersAccountLiquidator(default_context, default_wallet, group)\n",
|
||||
" print(force_cancel_orders_account_liquidator.prepare_instructions(margin_account, prices))\n"
|
||||
" force_cancel_orders_account_liquidator = ForceCancelOrdersAccountLiquidator(default_context, default_wallet)\n",
|
||||
" print(force_cancel_orders_account_liquidator.prepare_instructions(group, margin_account, prices))\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "advisory-questionnaire",
|
||||
"id": "urban-network",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# ⚠ Warning\n",
|
||||
|
@ -16,7 +16,7 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "expanded-season",
|
||||
"id": "amber-dairy",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# 🥭 Instructions\n",
|
||||
|
@ -27,7 +27,7 @@
|
|||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "italian-nelson",
|
||||
"id": "romance-extent",
|
||||
"metadata": {
|
||||
"jupyter": {
|
||||
"source_hidden": true
|
||||
|
@ -55,7 +55,7 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "elder-canada",
|
||||
"id": "dirty-valuation",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# InstructionBuilder class\n",
|
||||
|
@ -66,7 +66,7 @@
|
|||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "aerial-police",
|
||||
"id": "amber-continuity",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
|
@ -85,7 +85,7 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "trained-office",
|
||||
"id": "alert-found",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# ForceCancelOrdersInstructionBuilder class"
|
||||
|
@ -93,7 +93,7 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "controlling-prince",
|
||||
"id": "empirical-department",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Rust Interface\n",
|
||||
|
@ -125,7 +125,7 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "reflected-energy",
|
||||
"id": "dependent-truck",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Client API call\n",
|
||||
|
@ -169,7 +169,7 @@
|
|||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "protective-frequency",
|
||||
"id": "distant-johns",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
|
@ -275,7 +275,7 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "surface-vessel",
|
||||
"id": "passing-canberra",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# LiquidateInstructionBuilder class\n",
|
||||
|
@ -287,7 +287,7 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "confidential-house",
|
||||
"id": "active-essay",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Rust Interface\n",
|
||||
|
@ -333,7 +333,7 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "professional-memorial",
|
||||
"id": "amino-duncan",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Client API call\n",
|
||||
|
@ -370,7 +370,7 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "voluntary-communications",
|
||||
"id": "according-benefit",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## from_margin_account_and_market() function\n",
|
||||
|
@ -391,7 +391,7 @@
|
|||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "becoming-lawsuit",
|
||||
"id": "labeled-mattress",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
|
@ -490,7 +490,7 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "extended-hindu",
|
||||
"id": "accompanied-drill",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# 🏃 Running"
|
||||
|
@ -499,7 +499,7 @@
|
|||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "broadband-boost",
|
||||
"id": "complicated-brother",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "consistent-ceramic",
|
||||
"id": "satellite-eleven",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# ⚠ Warning\n",
|
||||
|
@ -16,7 +16,7 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "sound-transparency",
|
||||
"id": "taken-sound",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# 🥭 Liquidation\n",
|
||||
|
@ -31,7 +31,7 @@
|
|||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "ordered-share",
|
||||
"id": "shaped-plasma",
|
||||
"metadata": {
|
||||
"jupyter": {
|
||||
"source_hidden": true
|
||||
|
@ -49,7 +49,7 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "respective-indonesian",
|
||||
"id": "cosmetic-province",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 🦺 Safety\n",
|
||||
|
@ -63,7 +63,7 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "valued-bernard",
|
||||
"id": "analyzed-pollution",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 📇 Collateralisation Ratios Details\n",
|
||||
|
@ -81,7 +81,7 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "direct-fundamental",
|
||||
"id": "double-cross",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# 💧 Liquidation Process"
|
||||
|
@ -89,7 +89,7 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "received-distance",
|
||||
"id": "indie-villa",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 📇 Steps\n",
|
||||
|
@ -117,7 +117,7 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "gothic-qualification",
|
||||
"id": "downtown-horse",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# 🏃 Running\n",
|
||||
|
@ -134,7 +134,7 @@
|
|||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "celtic-python",
|
||||
"id": "patent-lodging",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
|
@ -144,7 +144,7 @@
|
|||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "expected-fellow",
|
||||
"id": "solved-summit",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
|
@ -170,8 +170,8 @@
|
|||
" margin_account = MarginAccount.load(default_context, PublicKey(MARGIN_ACCOUNT_TO_LIQUIDATE), group)\n",
|
||||
" intrinsic_balance_sheets_before = margin_account.get_intrinsic_balance_sheets(group)\n",
|
||||
" print(\"Margin Account Before:\", intrinsic_balance_sheets_before)\n",
|
||||
" liquidator = ForceCancelOrdersAccountLiquidator(default_context, default_wallet, group)\n",
|
||||
" transaction_id = liquidator.liquidate(margin_account, prices)\n",
|
||||
" liquidator = ForceCancelOrdersAccountLiquidator(default_context, default_wallet)\n",
|
||||
" transaction_id = liquidator.liquidate(group, margin_account, prices)\n",
|
||||
" if transaction_id is None:\n",
|
||||
" print(\"No transaction sent.\")\n",
|
||||
" else:\n",
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "rotary-tsunami",
|
||||
"id": "executed-lithuania",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# ⚠ Warning\n",
|
||||
|
@ -16,7 +16,7 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "imperial-observation",
|
||||
"id": "latest-vacuum",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# 🥭 SimpleLiquidator\n",
|
||||
|
@ -27,7 +27,7 @@
|
|||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "clear-ultimate",
|
||||
"id": "rising-graham",
|
||||
"metadata": {
|
||||
"jupyter": {
|
||||
"source_hidden": true
|
||||
|
@ -38,7 +38,7 @@
|
|||
"import logging\n",
|
||||
"import typing\n",
|
||||
"\n",
|
||||
"from AccountLiquidator import ForceCancelOrdersAccountLiquidator\n",
|
||||
"from AccountLiquidator import AccountLiquidator\n",
|
||||
"from BaseModel import Group, MarginAccount, MarginAccountMetadata, OpenOrders, TokenValue\n",
|
||||
"from Context import Context\n",
|
||||
"from Wallet import Wallet\n",
|
||||
|
@ -47,7 +47,7 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "nuclear-greenhouse",
|
||||
"id": "disciplinary-plant",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# SimpleLiquidator class\n",
|
||||
|
@ -69,27 +69,28 @@
|
|||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "verified-boring",
|
||||
"id": "adjacent-burton",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def token_balances_from_wallet(context: Context, wallet: Wallet, group: Group) -> typing.List[TokenValue]:\n",
|
||||
" balances: typing.List[TokenValue] = []\n",
|
||||
" for token in group.tokens:\n",
|
||||
" balance = TokenValue(token, context.fetch_token_balance(wallet.address, token.mint))\n",
|
||||
" balances += [balance]\n",
|
||||
"\n",
|
||||
" return balances\n",
|
||||
"\n",
|
||||
"class SimpleLiquidator:\n",
|
||||
" def __init__(self, context: Context, wallet: Wallet, wallet_balancer: WalletBalancer, worthwhile_threshold: float = 0.01):\n",
|
||||
" def __init__(self, context: Context, wallet: Wallet, account_liquidator: AccountLiquidator, wallet_balancer: WalletBalancer, worthwhile_threshold: float = 0.01):\n",
|
||||
" self.logger: logging.Logger = logging.getLogger(self.__class__.__name__)\n",
|
||||
" self.context = context\n",
|
||||
" self.wallet = wallet\n",
|
||||
" self.account_liquidator: AccountLiquidator = account_liquidator\n",
|
||||
" self.wallet_balancer = wallet_balancer\n",
|
||||
" self.worthwhile_threshold = worthwhile_threshold\n",
|
||||
"\n",
|
||||
" def run(self):\n",
|
||||
" def _token_balances_from_wallet(context: Context, wallet: Wallet, group: Group) -> typing.List[TokenValue]:\n",
|
||||
" balances: typing.List[TokenValue] = []\n",
|
||||
" for token in group.tokens:\n",
|
||||
" balance = TokenValue(token, context.fetch_token_balance(wallet.address, token.mint))\n",
|
||||
" balances += [balance]\n",
|
||||
"\n",
|
||||
" return balances\n",
|
||||
"\n",
|
||||
" self.logger.info(\"Fetching all margin accounts...\")\n",
|
||||
" group = Group.load(self.context)\n",
|
||||
" prices = group.fetch_token_prices()\n",
|
||||
|
@ -122,13 +123,12 @@
|
|||
" self.logger.info(\"No accounts to liquidate.\")\n",
|
||||
" return\n",
|
||||
"\n",
|
||||
" liquidator = ForceCancelOrdersAccountLiquidator(self.context, self.wallet, group)\n",
|
||||
" for mam in highest_first:\n",
|
||||
" wallet_balances_before = token_balances_from_wallet(self.context, self.wallet, group)\n",
|
||||
" wallet_balances_before = _token_balances_from_wallet(self.context, self.wallet, group)\n",
|
||||
" self.logger.info(f\"Wallet balances before:\\n{wallet_balances_before}\")\n",
|
||||
" self.logger.info(f\"Margin account balances before:\\n{mam.balances}\")\n",
|
||||
" self.logger.info(f\"Liquidating margin account: {mam.margin_account}\\n{mam.balance_sheet}\")\n",
|
||||
" transaction_id = liquidator.liquidate(mam.margin_account, prices)\n",
|
||||
" transaction_id = self.account_liquidator.liquidate(group, mam.margin_account, prices)\n",
|
||||
" if transaction_id is None:\n",
|
||||
" self.logger.info(\"No transaction sent.\")\n",
|
||||
" else:\n",
|
||||
|
@ -140,14 +140,14 @@
|
|||
" margin_account_after_liquidation = MarginAccount.load(self.context, mam.margin_account.address, group_after)\n",
|
||||
" intrinsic_balances_after = margin_account_after_liquidation.get_intrinsic_balances(group_after)\n",
|
||||
" self.logger.info(f\"Margin account balances after: {intrinsic_balances_after}\")\n",
|
||||
" wallet_balances_after = token_balances_from_wallet(self.context, self.wallet, group)\n",
|
||||
" wallet_balances_after = _token_balances_from_wallet(self.context, self.wallet, group)\n",
|
||||
" self.logger.info(f\"Wallet balances after:\\n{wallet_balances_after}\")\n",
|
||||
" self.wallet_balancer(prices)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "colored-stable",
|
||||
"id": "nutritional-virginia",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# 🏃 Running"
|
||||
|
@ -156,13 +156,14 @@
|
|||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "frank-decade",
|
||||
"id": "unable-means",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"if __name__ == \"__main__\":\n",
|
||||
" logging.getLogger().setLevel(logging.INFO)\n",
|
||||
"\n",
|
||||
" from AccountLiquidator import NullAccountLiquidator\n",
|
||||
" from Context import default_context\n",
|
||||
" from Wallet import default_wallet\n",
|
||||
" from WalletBalancer import NullWalletBalancer\n",
|
||||
|
@ -170,7 +171,7 @@
|
|||
" if default_wallet is None:\n",
|
||||
" print(\"No default wallet file available.\")\n",
|
||||
" else:\n",
|
||||
" liquidator = SimpleLiquidator(default_context, default_wallet, NullWalletBalancer())\n",
|
||||
" liquidator = SimpleLiquidator(default_context, default_wallet, NullAccountLiquidator(), NullWalletBalancer())\n",
|
||||
" liquidator.run()\n"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "minor-wisconsin",
|
||||
"id": "angry-beast",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# ⚠ Warning\n",
|
||||
|
@ -16,7 +16,7 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "electronic-michael",
|
||||
"id": "corporate-basis",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# 🥭 TradeExecutor\n",
|
||||
|
@ -31,7 +31,7 @@
|
|||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "rubber-latin",
|
||||
"id": "driven-masters",
|
||||
"metadata": {
|
||||
"jupyter": {
|
||||
"source_hidden": true
|
||||
|
@ -59,7 +59,7 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "historic-auckland",
|
||||
"id": "complicated-ebony",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# TradeExecutor class\n",
|
||||
|
@ -77,7 +77,7 @@
|
|||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "combined-spank",
|
||||
"id": "tribal-strategy",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
|
@ -96,7 +96,35 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "surgical-tradition",
|
||||
"id": "convertible-domain",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## NullTradeExecutor class\n",
|
||||
"\n",
|
||||
"A null, no-op, dry-run trade executor that can be plugged in anywhere a `TradeExecutor` is expected, but which will not actually trade."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "bored-bulgarian",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class NullTradeExecutor(TradeExecutor):\n",
|
||||
" def __init__(self):\n",
|
||||
" super().__init__()\n",
|
||||
"\n",
|
||||
" def buy(self, symbol: str, quantity: Decimal):\n",
|
||||
" self.logger.info(f\"Skipping BUY trade of {quantity:,.8f} of '{symbol}'.\")\n",
|
||||
"\n",
|
||||
" def sell(self, symbol: str, quantity: Decimal):\n",
|
||||
" self.logger.info(f\"Skipping SELL trade of {quantity:,.8f} of '{symbol}'.\")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "alien-extent",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# SerumImmediateTradeExecutor class\n",
|
||||
|
@ -126,7 +154,7 @@
|
|||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "finished-craps",
|
||||
"id": "ceramic-newport",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
|
@ -295,7 +323,7 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "polar-chance",
|
||||
"id": "developmental-internet",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# 🏃 Running\n",
|
||||
|
@ -308,7 +336,7 @@
|
|||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "welsh-pension",
|
||||
"id": "consecutive-pasta",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
|
|
|
@ -30,7 +30,7 @@ from decimal import Decimal
|
|||
from BaseModel import Group
|
||||
from Constants import WARNING_DISCLAIMER_TEXT
|
||||
from Context import Context, default_cluster, default_cluster_url, default_program_id, default_dex_program_id, default_group_name, default_group_id
|
||||
from TradeExecutor import SerumImmediateTradeExecutor
|
||||
from TradeExecutor import NullTradeExecutor, SerumImmediateTradeExecutor, TradeExecutor
|
||||
from Wallet import Wallet
|
||||
from WalletBalancer import TargetBalanceParser, WalletBalancer
|
||||
|
||||
|
@ -59,6 +59,8 @@ parser.add_argument("--action-threshold", type=Decimal, default=Decimal("0.01"),
|
|||
help="fraction of total wallet value a trade must be above to be carried out")
|
||||
parser.add_argument("--adjustment-factor", type=Decimal, default=Decimal("0.05"),
|
||||
help="factor by which to adjust the SELL price (akin to maximum slippage)")
|
||||
parser.add_argument("--dry-run", type=bool,
|
||||
help="runs as read-only and does not perform any transactions")
|
||||
args = parser.parse_args()
|
||||
|
||||
logging.getLogger().setLevel(args.log_level)
|
||||
|
@ -88,7 +90,10 @@ try:
|
|||
prices = group.fetch_token_prices()
|
||||
print(prices)
|
||||
|
||||
trade_executor = SerumImmediateTradeExecutor(context, wallet, group, adjustment_factor)
|
||||
if args.dry_run:
|
||||
trade_executor: TradeExecutor = NullTradeExecutor()
|
||||
else:
|
||||
trade_executor = SerumImmediateTradeExecutor(context, wallet, group, adjustment_factor)
|
||||
|
||||
wallet_balancer = WalletBalancer(context, wallet, trade_executor, action_threshold, group.tokens, targets)
|
||||
wallet_balancer.balance(prices)
|
||||
|
|
|
@ -30,7 +30,7 @@ from decimal import Decimal
|
|||
from BaseModel import Group
|
||||
from Constants import WARNING_DISCLAIMER_TEXT
|
||||
from Context import Context, default_cluster, default_cluster_url, default_program_id, default_dex_program_id, default_group_name, default_group_id
|
||||
from TradeExecutor import SerumImmediateTradeExecutor
|
||||
from TradeExecutor import NullTradeExecutor, SerumImmediateTradeExecutor, TradeExecutor
|
||||
from Wallet import Wallet
|
||||
|
||||
# We explicitly want argument parsing to be outside the main try-except block because some arguments
|
||||
|
@ -56,6 +56,8 @@ parser.add_argument("--token-symbol", type=str, required=True, help="token symbo
|
|||
parser.add_argument("--quantity", type=Decimal, required=True, help="quantity of token to buy")
|
||||
parser.add_argument("--adjustment-factor", type=Decimal, default=Decimal("0.05"),
|
||||
help="factor by which to adjust the BUY price (akin to maximum slippage)")
|
||||
parser.add_argument("--dry-run", type=bool,
|
||||
help="runs as read-only and does not perform any transactions")
|
||||
args = parser.parse_args()
|
||||
|
||||
logging.getLogger().setLevel(args.log_level)
|
||||
|
@ -79,7 +81,11 @@ try:
|
|||
|
||||
symbol = args.token_symbol.upper()
|
||||
|
||||
trade_executor = SerumImmediateTradeExecutor(context, wallet, group, adjustment_factor)
|
||||
if args.dry_run:
|
||||
trade_executor: TradeExecutor = NullTradeExecutor()
|
||||
else:
|
||||
trade_executor = SerumImmediateTradeExecutor(context, wallet, group, adjustment_factor)
|
||||
|
||||
trade_executor.buy(symbol, args.quantity)
|
||||
|
||||
logging.info("Buy completed.")
|
||||
|
|
|
@ -30,7 +30,7 @@ from decimal import Decimal
|
|||
from BaseModel import Group
|
||||
from Constants import WARNING_DISCLAIMER_TEXT
|
||||
from Context import Context, default_cluster, default_cluster_url, default_program_id, default_dex_program_id, default_group_name, default_group_id
|
||||
from TradeExecutor import SerumImmediateTradeExecutor
|
||||
from TradeExecutor import NullTradeExecutor, SerumImmediateTradeExecutor, TradeExecutor
|
||||
from Wallet import Wallet
|
||||
|
||||
# We explicitly want argument parsing to be outside the main try-except block because some arguments
|
||||
|
@ -56,6 +56,8 @@ parser.add_argument("--token-symbol", type=str, required=True, help="token symbo
|
|||
parser.add_argument("--quantity", type=Decimal, required=True, help="quantity of token to buy")
|
||||
parser.add_argument("--adjustment-factor", type=Decimal, default=Decimal("0.05"),
|
||||
help="factor by which to adjust the SELL price (akin to maximum slippage)")
|
||||
parser.add_argument("--dry-run", type=bool,
|
||||
help="runs as read-only and does not perform any transactions")
|
||||
args = parser.parse_args()
|
||||
|
||||
logging.getLogger().setLevel(args.log_level)
|
||||
|
@ -79,7 +81,11 @@ try:
|
|||
|
||||
symbol = args.token_symbol.upper()
|
||||
|
||||
trade_executor = SerumImmediateTradeExecutor(context, wallet, group, adjustment_factor)
|
||||
if args.dry_run:
|
||||
trade_executor: TradeExecutor = NullTradeExecutor()
|
||||
else:
|
||||
trade_executor = SerumImmediateTradeExecutor(context, wallet, group, adjustment_factor)
|
||||
|
||||
trade_executor.sell(symbol, args.quantity)
|
||||
|
||||
logging.info("Sell completed.")
|
||||
|
|
|
@ -28,7 +28,7 @@ import traceback
|
|||
from solana.publickey import PublicKey
|
||||
|
||||
from AccountScout import AccountScout
|
||||
from AccountLiquidator import ForceCancelOrdersAccountLiquidator
|
||||
from AccountLiquidator import AccountLiquidator, ForceCancelOrdersAccountLiquidator, NullAccountLiquidator
|
||||
from BaseModel import Group, MarginAccount
|
||||
from Constants import WARNING_DISCLAIMER_TEXT
|
||||
from Context import Context, default_cluster, default_cluster_url, default_program_id, default_dex_program_id, default_group_name, default_group_id
|
||||
|
@ -55,6 +55,8 @@ parser.add_argument("--log-level", default=logging.INFO, type=lambda level: geta
|
|||
help="level of verbosity to log (possible values: DEBUG, INFO, WARNING, ERROR, CRITICAL)")
|
||||
parser.add_argument('--margin-account-address', type=PublicKey,
|
||||
help="Solana address of the Mango Markets margin account to be liquidated")
|
||||
parser.add_argument("--dry-run", type=bool,
|
||||
help="runs as read-only and does not perform any transactions")
|
||||
args = parser.parse_args()
|
||||
|
||||
logging.getLogger().setLevel(args.log_level)
|
||||
|
@ -96,8 +98,13 @@ try:
|
|||
margin_account = MarginAccount.load(context, margin_account_address, group)
|
||||
intrinsic_balance_sheets_before = margin_account.get_intrinsic_balance_sheets(group)
|
||||
print("Margin Account Before:", intrinsic_balance_sheets_before)
|
||||
liquidator = ForceCancelOrdersAccountLiquidator(context, wallet, group)
|
||||
transaction_id = liquidator.liquidate(margin_account, prices)
|
||||
|
||||
if args.dry_run:
|
||||
account_liquidator: AccountLiquidator = NullAccountLiquidator()
|
||||
else:
|
||||
account_liquidator = ForceCancelOrdersAccountLiquidator(context, wallet)
|
||||
|
||||
transaction_id = account_liquidator.liquidate(group, margin_account, prices)
|
||||
if transaction_id is None:
|
||||
print("No transaction sent.")
|
||||
else:
|
||||
|
|
|
@ -29,6 +29,7 @@ import traceback
|
|||
from decimal import Decimal
|
||||
|
||||
from AccountScout import AccountScout
|
||||
from AccountLiquidator import AccountLiquidator, ForceCancelOrdersAccountLiquidator, NullAccountLiquidator
|
||||
from BaseModel import Group
|
||||
from Constants import WARNING_DISCLAIMER_TEXT
|
||||
from Context import Context, default_cluster, default_cluster_url, default_program_id, default_dex_program_id, default_group_name, default_group_id
|
||||
|
@ -64,6 +65,8 @@ parser.add_argument("--action-threshold", type=Decimal, default=Decimal("0.01"),
|
|||
help="fraction of total wallet value a trade must be above to be carried out")
|
||||
parser.add_argument("--adjustment-factor", type=Decimal, default=Decimal("0.05"),
|
||||
help="factor by which to adjust the SELL price (akin to maximum slippage)")
|
||||
parser.add_argument("--dry-run", type=bool,
|
||||
help="runs as read-only and does not perform any transactions")
|
||||
args = parser.parse_args()
|
||||
|
||||
logging.getLogger().setLevel(args.log_level)
|
||||
|
@ -97,7 +100,12 @@ try:
|
|||
|
||||
logging.info("Wallet accounts OK.")
|
||||
|
||||
if (args.target is None) or (len(args.target) == 0):
|
||||
if args.dry_run:
|
||||
account_liquidator: AccountLiquidator = NullAccountLiquidator()
|
||||
else:
|
||||
account_liquidator = ForceCancelOrdersAccountLiquidator(context, wallet)
|
||||
|
||||
if args.dry_run or (args.target is None) or (len(args.target) == 0):
|
||||
wallet_balancer = NullWalletBalancer()
|
||||
else:
|
||||
balance_parser = TargetBalanceParser(group.tokens)
|
||||
|
@ -106,7 +114,7 @@ try:
|
|||
wallet_balancer = LiveWalletBalancer(context, wallet, trade_executor, action_threshold, group.tokens, targets)
|
||||
|
||||
stop = False
|
||||
liquidator = SimpleLiquidator(context, wallet, wallet_balancer)
|
||||
liquidator = SimpleLiquidator(context, wallet, account_liquidator, wallet_balancer)
|
||||
while not stop:
|
||||
started_at = time.time()
|
||||
try:
|
||||
|
|
|
@ -26,6 +26,7 @@ import projectsetup
|
|||
import traceback
|
||||
|
||||
from AccountScout import AccountScout
|
||||
from AccountLiquidator import AccountLiquidator, ForceCancelOrdersAccountLiquidator, NullAccountLiquidator
|
||||
from BaseModel import Group
|
||||
from Constants import WARNING_DISCLAIMER_TEXT
|
||||
from Context import Context, default_cluster, default_cluster_url, default_program_id, default_dex_program_id, default_group_name, default_group_id
|
||||
|
@ -52,6 +53,8 @@ parser.add_argument("--id-file", type=str, default="id.json",
|
|||
help="file containing the JSON-formatted wallet private key")
|
||||
parser.add_argument("--log-level", default=logging.INFO, type=lambda level: getattr(logging, level),
|
||||
help="level of verbosity to log (possible values: DEBUG, INFO, WARNING, ERROR, CRITICAL)")
|
||||
parser.add_argument("--dry-run", type=bool,
|
||||
help="runs as read-only and does not perform any transactions")
|
||||
args = parser.parse_args()
|
||||
|
||||
logging.getLogger().setLevel(args.log_level)
|
||||
|
@ -81,8 +84,13 @@ try:
|
|||
|
||||
logging.info("Wallet accounts OK.")
|
||||
|
||||
if args.dry_run:
|
||||
account_liquidator: AccountLiquidator = NullAccountLiquidator()
|
||||
else:
|
||||
account_liquidator = ForceCancelOrdersAccountLiquidator(context, wallet)
|
||||
|
||||
wallet_balancer = NullWalletBalancer()
|
||||
liquidator = SimpleLiquidator(context, wallet, wallet_balancer)
|
||||
liquidator = SimpleLiquidator(context, wallet, account_liquidator, wallet_balancer)
|
||||
liquidator.run()
|
||||
|
||||
except Exception as exception:
|
||||
|
|
Loading…
Reference in New Issue