mango-explorer/AccountLiquidator.ipynb

250 lines
9.6 KiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"cells": [
{
"cell_type": "markdown",
"id": "nominated-blind",
"metadata": {},
"source": [
"# ⚠ Warning\n",
"\n",
"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.\n",
"\n",
"[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gl/OpinionatedGeek%2Fmango-explorer/HEAD?filepath=AccountLiquidator.ipynb) _🏃 To run this notebook press the ⏩ icon in the toolbar above._\n",
"\n",
"[🥭 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)"
]
},
{
"cell_type": "markdown",
"id": "coupled-butterfly",
"metadata": {},
"source": [
"# 🥭 AccountLiquidator\n",
"\n",
"An `AccountLiquidator` takes a `MarginAccount` and liquidates it (if possible)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "acceptable-porter",
"metadata": {
"jupyter": {
"source_hidden": true
}
},
"outputs": [],
"source": [
"import logging\n",
"import typing\n",
"\n",
"from solana.transaction import Transaction\n",
"\n",
"from BaseModel import Group, MarginAccount, TokenValue\n",
"from Context import Context\n",
"from Instructions import ForceCancelOrdersInstructionBuilder, InstructionBuilder, LiquidateInstructionBuilder\n",
"from Wallet import Wallet\n"
]
},
{
"cell_type": "markdown",
"id": "scientific-origin",
"metadata": {},
"source": [
"# 💧 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)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "remarkable-discretion",
"metadata": {},
"outputs": [],
"source": [
"class AccountLiquidator:\n",
" def __init__(self, context: Context, wallet: Wallet, group: Group):\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",
" liquidate_instructions: typing.List[InstructionBuilder] = []\n",
" liquidate_instruction = LiquidateInstructionBuilder.from_margin_account_and_market(self.context, self.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",
"\n",
" if len(instructions) == 0:\n",
" return None\n",
"\n",
" transaction = Transaction()\n",
" for instruction in instructions:\n",
" transaction.add(instruction.build())\n",
"\n",
" for instruction in transaction.instructions:\n",
" self.logger.debug(\"INSTRUCTION\")\n",
" self.logger.debug(\" Keys:\")\n",
" for key in instruction.keys:\n",
" self.logger.debug(\" \", f\"{key.pubkey}\".ljust(45), f\"{key.is_signer}\".ljust(6), f\"{key.is_writable}\".ljust(6))\n",
" self.logger.debug(\" Data:\", \" \".join(f\"{x:02x}\" for x in instruction.data))\n",
" 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",
"\n"
]
},
{
"cell_type": "markdown",
"id": "otherwise-framework",
"metadata": {},
"source": [
"# 🌪️ ForceCancelOrdersAccountLiquidator class\n",
"\n",
"When liquidating an account, it's a good idea to ensure it has no open orders that could lock funds. This is why Mango allows a liquidator to force-close orders on a liquidatable account.\n",
"\n",
"`ForceCancelOrdersAccountLiquidator` overrides `prepare_instructions()` to inject any necessary force-cancel instructions before the `PartialLiquidate` instruction.\n",
"\n",
"This is not always necessary. For example, if the liquidator is partially-liquidating a large account, then perhaps only the first partial-liquidate needs to check and force-close orders, and subsequent partial liquidations can skip this step as an optimisation.\n",
"\n",
"The separation of the regular `AccountLiquidator` and the `ForceCancelOrdersAccountLiquidator` classes allows the caller to determine which process is used."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "arctic-issue",
"metadata": {},
"outputs": [],
"source": [
"class ForceCancelOrdersAccountLiquidator(AccountLiquidator):\n",
" def __init__(self, context: Context, wallet: Wallet, group: Group):\n",
" super().__init__(context, wallet, group)\n",
"\n",
" def prepare_instructions(self, 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",
" 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",
"\n",
" all_instructions = force_cancel_orders_instructions + super().prepare_instructions(margin_account, prices)\n",
"\n",
" return all_instructions\n"
]
},
{
"cell_type": "markdown",
"id": "biblical-survey",
"metadata": {},
"source": [
"# 🏃 Running"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "moved-penny",
"metadata": {},
"outputs": [],
"source": [
"if __name__ == \"__main__\":\n",
" logging.getLogger().setLevel(logging.INFO)\n",
"\n",
" from Context import default_context\n",
" from Wallet import default_wallet\n",
"\n",
" if default_wallet is None:\n",
" print(\"No default wallet file available.\")\n",
" else:\n",
" 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",
"\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"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.6"
},
"toc": {
"base_numbering": 1,
"nav_menu": {},
"number_sections": true,
"sideBar": true,
"skip_h1_title": false,
"title_cell": "Table of Contents",
"title_sidebar": "Contents",
"toc_cell": false,
"toc_position": {},
"toc_section_display": true,
"toc_window_display": true
},
"varInspector": {
"cols": {
"lenName": 16,
"lenType": 16,
"lenVar": 40
},
"kernels_config": {
"python": {
"delete_cmd_postfix": "",
"delete_cmd_prefix": "del ",
"library": "var_list.py",
"varRefreshCmd": "print(var_dic_list())"
},
"r": {
"delete_cmd_postfix": ") ",
"delete_cmd_prefix": "rm(",
"library": "var_list.r",
"varRefreshCmd": "cat(var_dic_list()) "
}
},
"types_to_exclude": [
"module",
"function",
"builtin_function_or_method",
"instance",
"_Feature"
],
"window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 5
}