mango-explorer/Liquidation.ipynb

259 lines
11 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": "bigger-murder",
"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=Liquidation.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": "structured-spare",
"metadata": {},
"source": [
"# 🥭 Liquidation\n",
"\n",
"Mango Markets margin accounts must hold more assets than liabilities.\n",
"\n",
"If a margin account doesn't hold assets worth more than the _maintenance collateral ratio_ (currently 110%) of its liabilities, anyone can 'pay off' some of the liabilities in that account. In return, that payer receives 105% of their tokens back.\n",
"\n",
"This process - called '_liquidation_' - continues until the remaining assets are worth more than the _initial collateral ratio_ (currently 120%) of the remaining liabilities, or until there are no more collateral tokens to return to liquidators.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "incident-springfield",
"metadata": {
"jupyter": {
"source_hidden": true
}
},
"outputs": [],
"source": [
"import logging\n",
"\n",
"from solana.publickey import PublicKey\n",
"\n",
"from BaseModel import Group, MarginAccount, TokenValue\n",
"from AccountLiquidator import ForceCancelOrdersAccountLiquidator\n"
]
},
{
"cell_type": "markdown",
"id": "enormous-yugoslavia",
"metadata": {},
"source": [
"## 🦺 Safety\n",
"\n",
"Liquidation is how Mango Markets protects accounts from systemic losses.\n",
"\n",
"If the collateralisation ratio were allowed to fall below 100%, that would represent a systemic risk to Mango Markets. Funds would have to be taken from the lending pools, affecting all lenders, which may in turn affect their collateralisation ratio and expose them to liquidation, possibly leading to a cascade of liquidations until the system stabilises.\n",
"\n",
"Liquidators promptly stepping in to 'buy' liquidatable accounts before they fall to less than 100% collateralisation prevent this instability.\n"
]
},
{
"cell_type": "markdown",
"id": "economic-pioneer",
"metadata": {},
"source": [
"## 📇 Collateralisation Ratios Details\n",
"\n",
"When trading with leverage on Mango Markets, you start by adding assets to your margin account. Your margin account must hold assets worth more than 120% of the liabilities. This ratio of assets to liabilities is called your '_collateralisation ratio_'.\n",
"\n",
"If a new trade would mean your assets would be worth less 120% of the libilities - that your collateralisation ratio would be less than 120% - you will not be allowed to place the trade. No existing trades are closed, you just can't place any new orders.\n",
"\n",
"The value of assets and liabilities is derived from the prices from on-chain oracles.\n",
"\n",
"Prices change, so even if no new trades are made the value of assets can fall and the liabilities can increase, changing the collateralisation ratio.\n",
"\n",
"**If the collateralisation ratio falls below 110%, your margin account can be '_liquidated_', resulting in the loss of some or a all assets in the account.**\n"
]
},
{
"cell_type": "markdown",
"id": "certain-discrimination",
"metadata": {},
"source": [
"# 💧 Liquidation Process"
]
},
{
"cell_type": "markdown",
"id": "secondary-occupation",
"metadata": {},
"source": [
"## 📇 Steps\n",
"\n",
"The liquidation process involves paying off some or all of one of the liabilities in the under-collateralised margin account. Margin accounts can have liabilities in multiple tokens, but liquidation can only be performed on one token at a time.\n",
"\n",
"These are the basic steps to liquidate an account:\n",
"1. (Optional) Force cancellation of all outstanding orders for the margin account in that market.\n",
"2. Build and send the `PartialLiquidate` instruction.\n",
"3. Repeat step 2 (if necessary) with fresh tokens.\n",
"\n",
"Step 1 is optional. Open orders can lock funds in an account, preventing liquidation, but it may be desirable for liquidators to optimise their liquidation process by only force-cancelling orders on that margin account's first partial-liquidation. The margin account is unlikely to have been able to open new orders between partial-liquidations if they happen quickly enough.\n",
"\n",
"To actually run a liquidator, however, there are probably some additional steps:\n",
"1. Find all liquidatable margin accounts.\n",
"2. Pick the most appropriate of these margin accounts, based on that account's collatoralisation and the liquidator's token balances.\n",
"3. Pick the market with the most value in the margin account's openorders accounts.\n",
"4. Force cancellation of all outstanding orders for the margin account in that market.\n",
"5. Build and send the `PartialLiquidate` instruction.\n",
"6. Convert the received tokens to your desired tokens.\n",
"7. Repeat from step 2 (if necessary) with fresh tokens.\n",
"\n"
]
},
{
"cell_type": "markdown",
"id": "graphic-devon",
"metadata": {},
"source": [
"# 🏃 Running\n",
"\n",
"This section allows you to perform a liquidation on a specific `MarginAccount`.\n",
"\n",
"To do this you will need:\n",
"* The `PublicKey` of the margin account - put this in the `MARGIN_ACCOUNT_TO_LIQUIDATE` variable below, and\n",
"* A wallet with appropriate funds and accounts for Mango Markets liquidation (see [AccountScout](AccountScout.ipynb) for details). The private key for this wallet should be placed in the `id.json` file.\n",
"\n",
"You can then 🏃‍♀️ run this notebook by pressing the ⏩ icon in the toolbar at the top of the page."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "anticipated-situation",
"metadata": {},
"outputs": [],
"source": [
"MARGIN_ACCOUNT_TO_LIQUIDATE = \"\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "serious-depth",
"metadata": {},
"outputs": [],
"source": [
"if __name__ == \"__main__\":\n",
" logging.getLogger().setLevel(logging.INFO)\n",
"\n",
" if MARGIN_ACCOUNT_TO_LIQUIDATE == \"\":\n",
" raise Exception(\"No margin account to liquidate - try setting the variable MARGIN_ACCOUNT_TO_LIQUIDATE to a margin account public key.\")\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",
" print(\"Wallet Balances Before:\")\n",
" group = Group.load(default_context)\n",
" balances_before = group.fetch_balances(default_wallet.address)\n",
" TokenValue.report(print, balances_before)\n",
"\n",
" prices = group.fetch_token_prices()\n",
" 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)\n",
" transaction_id = liquidator.liquidate(group, margin_account, prices)\n",
" if transaction_id is None:\n",
" print(\"No transaction sent.\")\n",
" else:\n",
" print(\"Transaction ID:\", transaction_id)\n",
" print(\"Waiting for confirmation...\")\n",
"\n",
" default_context.wait_for_confirmation(transaction_id)\n",
"\n",
" group_after = Group.load(default_context)\n",
" margin_account_after_liquidation = MarginAccount.load(default_context, PublicKey(MARGIN_ACCOUNT_TO_LIQUIDATE), group_after)\n",
" intrinsic_balance_sheets_after = margin_account_after_liquidation.get_intrinsic_balance_sheets(group_after)\n",
" print(\"Margin Account After:\", intrinsic_balance_sheets_after)\n",
" print(\"Wallet Balances After:\")\n",
" balances_after = group_after.fetch_balances(default_wallet.address)\n",
" TokenValue.report(print, balances_after)\n",
"\n",
" print(\"Wallet Balances Changes:\")\n",
" changes = TokenValue.changes(balances_before, balances_after)\n",
" TokenValue.report(print, changes)\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
}