flash-loan-user-margin added

This commit is contained in:
fiftyeightandeight
2021-08-27 22:04:56 +08:00
parent 8babbb270f
commit 0e901258f0
21 changed files with 125458 additions and 20 deletions

View File

@@ -13,10 +13,6 @@ depends_on = ["trait-vault", "trait-sip-010", "trait-flash-loan-user", "math-fix
path = "contracts/alex-ytp-multisig-vote.clar"
depends_on = ["trait-multisig-vote", "pool-token-usda-ayusda", "yield-token-pool"]
[contracts.yield-usda-4380]
path = "contracts/yield-usda-4380.clar"
depends_on = ["trait-sip-010", "trait-yield-token", "token-usda"]
[contracts.collateral-rebalancing-pool]
path = "contracts/collateral-rebalancing-pool.clar"
depends_on = ["trait-sip-010", "trait-yield-token", "trait-vault", "math-fixed-point", "weighted-equation", "open-oracle", "fixed-weight-pool"]
@@ -25,6 +21,14 @@ depends_on = ["trait-sip-010", "trait-yield-token", "trait-vault", "math-fixed-p
path = "contracts/fixed-weight-pool.clar"
depends_on = ["trait-sip-010", "trait-pool-token", "trait-vault", "math-fixed-point", "weighted-equation"]
[contracts.flash-loan-user-margin]
path = "contracts/flash-loan-user-margin.clar"
depends_on = []
[contracts.flash-loan-user-test]
path = "contracts/flash-loan-user-test.clar"
depends_on = ["trait-flash-loan-user", "trait-vault", "trait-sip-010", "alex-vault", "fixed-weight-pool", "token-alex", "token-wbtc"]
[contracts.key-usda-wbtc-4380]
path = "contracts/key-usda-wbtc-4380.clar"
depends_on = ["trait-sip-010", "trait-yield-token", "token-usda"]
@@ -41,6 +45,10 @@ depends_on = ["math-log-exp"]
path = "contracts/math-log-exp.clar"
depends_on = []
[contracts.multisig-yield-usda-wbtc]
path = "contracts/multisig-yield-usda-wbtc.clar"
depends_on = ["trait-multisig-vote", "pool-token-usda-ayusda", "yield-token-pool"]
[contracts.open-oracle]
path = "contracts/open-oracle.clar"
depends_on = ["trait-yield-token", "trait-oracle", "trait-sip-010"]
@@ -49,26 +57,18 @@ depends_on = ["trait-yield-token", "trait-oracle", "trait-sip-010"]
path = "contracts/pool-token-alex-usda.clar"
depends_on = ["trait-sip-010", "trait-pool-token"]
[contracts.pool-token-yield-usda-wbtc]
path = "contracts/pool-token-yield-usda-wbtc.clar"
depends_on = ["trait-sip-010", "trait-pool-token"]
[contracts.pool-token-usda-ayusda]
path = "contracts/pool-token-usda-ayusda.clar"
depends_on = ["trait-sip-010", "trait-pool-token"]
[contracts.test-flash-loan-user]
path = "contracts/test-flash-loan-user.clar"
depends_on = ["trait-flash-loan-user", "trait-vault", "trait-sip-010", "alex-vault", "fixed-weight-pool", "token-alex", "token-wbtc"]
[contracts.pool-token-yield-usda-wbtc]
path = "contracts/pool-token-yield-usda-wbtc.clar"
depends_on = ["trait-sip-010", "trait-pool-token"]
[contracts.token-alex]
path = "contracts/token-alex.clar"
depends_on = ["trait-sip-010", "trait-alex-token"]
[contracts.yield-usda-18530]
path = "contracts/yield-usda-18530.clar"
depends_on = ["trait-sip-010", "trait-yield-token", "token-usda"]
[contracts.token-usda]
path = "contracts/token-usda.clar"
depends_on = ["trait-sip-010", "trait-alex-token"]
@@ -121,6 +121,10 @@ depends_on = ["math-fixed-point"]
path = "contracts/yield-token-pool.clar"
depends_on = ["trait-sip-010", "trait-pool-token", "trait-vault", "trait-flash-loan-user", "math-fixed-point", "yield-token-equation", "trait-yield-token", "open-oracle"]
[contracts.multisig-yield-usda-wbtc]
path = "contracts/multisig-yield-usda-wbtc.clar"
depends_on = ["trait-multisig-vote", "pool-token-usda-ayusda", "yield-token-pool"]
[contracts.yield-usda-18530]
path = "contracts/yield-usda-18530.clar"
depends_on = ["trait-sip-010", "trait-yield-token", "token-usda"]
[contracts.yield-usda-4380]
path = "contracts/yield-usda-4380.clar"
depends_on = ["trait-sip-010", "trait-yield-token", "token-usda"]

View File

@@ -407,7 +407,7 @@
;;(try! (contract-call? .alex-multisig-registry mint-token the-key-token new-supply tx-sender))
(print { object: "pool", action: "liquidity-added", data: pool-updated })
(ok true)
(ok {yield-token: yield-new-supply, key-token: key-new-supply})
)
)
)

View File

@@ -0,0 +1,60 @@
(use-trait ft-trait .trait-sip-010.sip-010-trait)
(use-trait yield-token-trait .trait-yield-token.yield-token-trait)
(define-constant ONE_8 u100000000) ;; 8 decimal places
(define-constant insufficient-flash-loan-balance-err (err u3003))
(define-constant invalid-pool-err (err u2001))
(define-constant no-liquidity-err (err u2002))
(define-constant invalid-liquidity-err (err u2003))
(define-constant transfer-x-failed-err (err u3001))
(define-constant transfer-y-failed-err (err u3002))
(define-constant pool-already-exists-err (err u2000))
(define-constant too-many-pools-err (err u2004))
(define-constant percent-greater-than-one (err u5000))
(define-constant no-fee-x-err (err u2005))
(define-constant no-fee-y-err (err u2006))
(define-constant weighted-equation-call-err (err u2009))
(define-constant math-call-err (err u2010))
(define-constant internal-function-call-err (err u1001))
(define-constant get-weight-fail-err (err u2012))
(define-constant get-expiry-fail-err (err u2013))
(define-constant get-price-fail-err (err u2015))
(define-constant get-symbol-fail-err (err u6000))
(define-constant get-oracle-price-fail-err (err u7000))
(define-constant expiry-err (err u2017))
(define-constant get-balance-fail-err (err u6001))
;; flash loan fee rate
(define-data-var flash-loan-fee-rate uint u100000) ;;0.001%
(define-public (execute-margin-trade (token <ft-trait>) (collateral <ft-trait>) (yield-token <yield-token-trait>) (key-token <yield-token-trait>) (expiry uint) (dx uint))
(let
(
(pool (try! (contract-call? .collateral-rebalancing-pool get-pool-details token collateral expiry)))
(ltv (try! (contract-call? .collateral-rebalancing-pool get-ltv token collateral expiry)))
(borrow (unwrap! (contract-call? .math-fixed-point mul-up dx ltv) math-call-err))
(margin (unwrap! (contract-call? .math-fixed-point sub-fixed dx borrow) math-call-err))
(pre-bal-borrow (try! (contract-call? collateral get-balance (as-contract .alex-vault))))
(fee-with-principal (unwrap! (contract-call? .math-fixed-point add-fixed ONE_8 (var-get flash-loan-fee-rate)) math-call-err))
(borrow-with-fee (unwrap! (contract-call? .math-fixed-point mul-up borrow fee-with-principal) math-call-err))
)
(asserts! (> pre-bal-borrow borrow) insufficient-flash-loan-balance-err)
(try! (contract-call? .fixed-weight-pool swap-x-for-y token collateral u50000000 u50000000 margin))
(try! (contract-call? collateral transfer borrow (as-contract .alex-vault) tx-sender none))
(let
(
(minted (try! (contract-call? .collateral-rebalancing-pool add-to-position token collateral yield-token key-token dx)))
(minted-yield-token (get yield-token minted))
(minted-key-token (get key-token minted))
(swapped (unwrap! (contract-call? .yield-token-pool swap-y-for-x yield-token token minted-yield-token) no-liquidity-err))
(swapped-token (get dx swapped))
)
(try! (contract-call? .fixed-weight-pool swap-x-for-y token collateral u50000000 u50000000 swapped-token))
(contract-call? collateral transfer borrow-with-fee tx-sender (as-contract .alex-vault) none)
)
)
)

1277
clarity/out Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,26 @@
import { Clarinet, Tx, Chain, Account, types } from 'https://deno.land/x/clarinet@v0.14.0/index.ts';
import { assertEquals } from 'https://deno.land/std@0.90.0/testing/asserts.ts';
Clarinet.test({
name: "Ensure that <...>",
async fn(chain: Chain, accounts: Map<string, Account>) {
let block = chain.mineBlock([
/*
* Add transactions with:
* Tx.contractCall(...)
*/
]);
assertEquals(block.receipts.length, 0);
assertEquals(block.height, 2);
block = chain.mineBlock([
/*
* Add transactions with:
* Tx.contractCall(...)
*/
]);
assertEquals(block.receipts.length, 0);
assertEquals(block.height, 3);
},
});

View File

@@ -14,7 +14,7 @@ const ayUsda4380TokenAddress = "ST1HTBVD3JG9C05J7HBJTHGR0GGW7KXW28M5JS8QE.yield-
const alexVaultAddress = 'ST1HTBVD3JG9C05J7HBJTHGR0GGW7KXW28M5JS8QE.alex-vault';
const testFlashLoanUser =
'ST1HTBVD3JG9C05J7HBJTHGR0GGW7KXW28M5JS8QE.test-flash-loan-user';
'ST1HTBVD3JG9C05J7HBJTHGR0GGW7KXW28M5JS8QE.flash-loan-user-test';
/**
* Flash Loan Testing in Vault implementation.
*

3132
clarity/ut Normal file

File diff suppressed because it is too large Load Diff

BIN
diagrams/.DS_Store vendored Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

View File

@@ -0,0 +1,27 @@
[Arbitrageur]-(Go to Vault)
[Lender]-(Go to Yield Token Pool)
(Go to Yield Token Pool)-(Sell Token)
(Sell Token)-(Buy ayToken)
[Borrower]-(Go to Collateral Rebalancing Pool)
(Go to Collateral Rebalancing Pool)-(Deposit Collateral)
(Deposit Collateral)-(Mint ayToken)
(Mint ayToken)-(Go to Yield Token Pool)
(Go to Yield Token Pool)-(Sell ayToken)
(Sell ayToken)-(Buy Token)
[Liquidity Provider]-(Go to Yield Token Pool)
(Go to Yield Token Pool)-(Deposit ayToken & Token)
(Deposit ayToken & Token)-(Mint Yield Token Pool Token)
[Flash Loan User]-(Go to Vault)
(Go to Vault)-(Create Flash Loan)
(Create Flash Loan)-(Sell Token)
(Create Flash Loan)-(Sell ayToken)
(Sell ayToken)-(Buy Token)
(Sell Token)-(Buy ayToken)
(Fee goes to Reserve)-(note: Reserve constitutes "net income" of ALEX and consists of Platform Fee and Flash Loan Fee. gALEX represents proportional ownership of the Reserve{bg:cyan})
(Mint Yield Token Pool Token)-(note: represent proportional ownership of Yield Token Pool{bg:pink})
(Mint ayToken)-(note: ayToken is a Pool Token and represents proportional ownership of Collateral Rebalancing Pool{bg:pink})
(Deposit ayToken & Token)-(note: LP only{bg:beige})
(Sell ayToken)-(note: Traders only{bg:beige})
(Sell Token)-(note: Traders only{bg:beige})
(Go to Collateral Rebalancing Pool)-(note: Borrower is an LP to Collateral Rebalancing Pool. She provides the liquidity "collateral" to the pool and mints the pool token "ayToken"{bg:beige})
(Deposit Collateral)-(note: Note single sided liquidity. The pool will automatically split the collateral into a basket of collateral and Token{bg:beige})

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,182 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"def gen_data_vec_btc(t, y_price, x, LTV0, bs_vol, Growth_rate, Real_vol): \n",
" \n",
" \"\"\"\n",
" Purpose: Simulate a CRP performance during a loan life term \n",
" - x: collateral token (BTC)\n",
" - y: loan token (USDC)\n",
" Assumption: \n",
" 1. Rebalnce following a delta replicating strategy of a call option on Token / Collateral. \n",
" 2. Price percentage change (approximately equals to log(p(t+1)/p(t))) follows a linear growth treand \n",
" (slope = growth rate) plus a random walk Brownie motion. \n",
" Input: \n",
" - t: term of the loan \n",
" - y_price: price of loan token (e.g. USDC = 3e-5 btc) \n",
" - x: Collateral token (e.g. BTC)\n",
" - LTV0: inital loan to value at time 0 \n",
" - bs_vol: black shoral option volatilty \n",
" - Growth_rate: linear growth slope of y price \n",
" - Real_vol: growth Brownie motion volatility \n",
" \n",
" Output: \n",
" - A dataframe with columns: ['t', 'y_price', 'borrowed_y', 'ltv', 'x_locked', \n",
" 'y_locked', 'collateral_x', 'w_ytoken', 'PVCR'] \n",
" \n",
" \"\"\"\n",
" # loan life duation between t(i) - t(i+1)\n",
" delta_t = t[:-1]-t[1:]\n",
" \n",
" # token price percent change following a linear growth curve plus a random walk with volatility = Real_vol \n",
" rw = np.random.random(size = len(t)-1)\n",
" delta_price = delta_t * (Growth_rate - 0.5*Real_vol**2) + Real_vol *np.sqrt(delta_t)*np.random.normal(0, 1, len(t)-1) #norm.ppf(rw)\n",
" \n",
" # calcualte price with exponetial of cumulative sum of log price differences. \n",
" price_logdiff= np.hstack([0, delta_price]).cumsum()\n",
" price = y_price*np.exp(price_logdiff)\n",
" \n",
" # reblance weights by black scholes option equestion. \n",
" w_y = norm.cdf((price_logdiff[:-1] + t[:-1]*(0.5*bs_vol**2))/bs_vol/np.sqrt(t[:-1]))\n",
" \n",
" # set a boundary [1e-8, 1-1e-8] to avoid 0 as denominator \n",
" w_y[w_y< 1e-8] = 1e-8 \n",
" w_y[w_y> (1- 1e-8)] = 1- 1e-8\n",
" \n",
" # borrowed usd values in btc, depends on inital Collateral x, initial LTV0, initial y price and current price \n",
" borrowed_usd_btc = np.hstack([x*LTV0, x*LTV0*np.cumprod(price[1:]/price[:-1])]) \n",
" # Theoratical btc locked size given pool value, weigth and current price. \n",
" y_lock_change = w_y[1:] + (1-w_y[:-1])*price[:-2]/price[1:-1]*w_y[1:]/w_y[:-1]\n",
" y_locked = np.hstack([x/y_price*w_y[0],x/y_price*w_y[0]*y_lock_change.cumprod()])\n",
" # Theoratical usd locked size given pool value, weigth and current price. \n",
" x_locked = y_locked * (1-w_y) * price[:-1]/ w_y \n",
" # updated collateral_btc with new price \n",
" collateral_x = np.hstack([x,y_locked * price[1:] + x_locked]) \n",
" # divergence loss is defined as current collateral in btc over the initial caollateral \n",
" holdvalue = x\n",
" loss = collateral_x/holdvalue \n",
" # ltv is defined as current borrowed values over the current collateral value. LTV > 1 means loan is default. \n",
" ltv = borrowed_usd_btc/collateral_x \n",
" # formate the output file as a dataframe for downstream analysis \n",
" _temp= np.column_stack((t, price, borrowed_usd_btc, ltv, np.append(x_locked, x_locked[-1]),\\\n",
" np.append(y_locked, y_locked[-1]), collateral_x, np.append(w_y, w_y[-1]), loss))\n",
" out_vec = pd.DataFrame(_temp, columns = ['t', 'y_price', 'borrowed_y', 'ltv', 'x_locked', 'y_locked', 'collateral_x', 'w_ytoken', 'pvcr'])\n",
" return out_vec "
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"class PoolEngine: \n",
" \"\"\"\n",
" pool engine class\n",
" Attribute: \n",
" - x: token 1 (e.g BTC) balance\n",
" - y: token 2 (e.g USDC) balance\n",
" - w_x: weight of x \n",
" Method:\n",
" - sp(): spot price of one y token in terms of x token\n",
" - in-given-out(inTtype, amount): size of in given out amount \n",
" - out-given-in(inTtype, amount): size of our given in amount \n",
" - in_given_price(inTtype, p): size of in to bring spot price to p the price of out token as function of in token\n",
" - step(new_wx, oracle_p, verbose = 0): attributes updated if weights or price changes \n",
" \n",
" \"\"\"\n",
" def __init__(self, x, y, w_x):\n",
" self.x = x\n",
" self.y = y\n",
" self.w_x = w_x \n",
" assert 0<self.w_x<1 , \"weights must be in (0,1) !\"\n",
" \n",
" def step(self, new_wx, oracle_p, verbose=0):\n",
" assert 0<new_wx<1 , \"weights must be in (0,1) !\"\n",
" self.w_x = new_wx\n",
" spot_price = self.sp()\n",
" if spot_price < oracle_p: \n",
" delta_x = self.in_given_price('x', oracle_p)\n",
" delta_y = self.out_given_in('x', delta_x)\n",
" if verbose == 1:\n",
" print('delta_x={}, and delta_y={}, price ={}, actual_price={}'.\\\n",
" format(delta_x, delta_y, 1/oracle_p, delta_y/delta_x))\n",
" self.x, self.y = self.x+delta_x, self.y-delta_y \n",
" elif spot_price > oracle_p:\n",
" delta_y = self.in_given_price('y',1/oracle_p)\n",
" delta_x = self.out_given_in('y', delta_y)\n",
" if verbose == 1: \n",
" print('delta_x={}, and delta_y={}, price={}, actual_price={}'.\\\n",
" format(delta_x, delta_y, 1/oracle_p,delta_y/delta_x))\n",
" self.x, self.y = self.x-delta_x, self.y+delta_y \n",
" else:\n",
" delta_x = 0\n",
" return np.array([self.x, self.y, new_wx, spot_price, oracle_p, delta_x])\n",
" \n",
" def sp(self):\n",
" \n",
" w_y = 1- self.w_x\n",
" p = self.x*w_y/(self.y*self.w_x) \n",
" return p \n",
" \n",
" def in_given_out(self, inTtype, amount):\n",
" if inTtype == 'x':\n",
" assert 0<= amount < self.y, 'out amount must be greater than 0 and less than total'\n",
" ratio = (1-self.w_x)/self.w_x \n",
" Ain = self.x * ((self.y/(self.y -amount))**(ratio) -1)\n",
" else: \n",
" assert 0<= amount < self.x, 'out amount must be greater than 0 and less than total'\n",
" ratio = self.w_x/(1-self.w_x) \n",
" Ain = self.y * ((self.x/(self.x -amount))**(ratio) -1)\n",
" return Ain \n",
" \n",
" def out_given_in(self, inTtype, amount):\n",
" assert amount >= 0, 'amount must be greater than 0'\n",
" if inTtype == 'x':\n",
" ratio = self.w_x / (1-self.w_x)\n",
" Aout = self.y * (1- (self.x/(self.x+amount))**ratio) \n",
" else: \n",
" ratio = (1-self.w_x)/self.w_x \n",
" Aout = self.x * (1- (self.y/(self.y+amount))**ratio) \n",
" return Aout\n",
"\n",
" def in_given_price(self, inTtype, price): \n",
" assert price >0 , 'price must be greater than 0'\n",
" if inTtype == 'x':\n",
" sp_price = self.sp()\n",
" Ain = self.x * ((price/sp_price)**(1-self.w_x) - 1) \n",
" else: \n",
" sp_price = 1/self.sp()\n",
" Ain = self.y * ((price/sp_price)**self.w_x - 1) \n",
" return Ain \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.7.3"
}
},
"nbformat": 4,
"nbformat_minor": 4
}

View File

@@ -0,0 +1,433 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 27,
"metadata": {},
"outputs": [],
"source": [
"# plot of liquity \n",
"import scipy \n",
"import matplotlib.pyplot as plt\n",
"import matplotlib.animation\n",
"from matplotlib.widgets import Slider\n",
"import seaborn as sns\n",
"import numpy as np \n",
"import random\n",
"import pandas as pd \n",
"from ipywidgets import *\n",
"from scipy.stats import norm\n",
"random.seed(1)\n",
"t = np.linspace(91,0,92)/10\n",
"Real_vol = 0.4\n",
"Growth_rate = 2\n",
"LTV0 = 0.9\n",
"bs_vol = 0.5\n",
"y_price = 50000\n",
"Collateral = 10000000\n",
"pool_init_x = 5000000\n",
"pool_init_y = 100\n",
"pool_init_wx = 0.5\n",
"# temp = weights_generator(t, y_price, bs_vol, Growth_rate, Real_vol, ma_window=20)\n",
"# temp.describe()\n",
"#_wt_ma= np.array([1, 2, 3])\n",
"# np.append([0], (_wt_ma[:-1]/_wt_ma[1:])**_wt_ma[1:]*( (1-_wt_ma[:-1])/(1-_wt_ma[1:]))**(1-_wt_ma[1:])-1)\n",
"# np.append([np.array([1,2]), np.convolve(np.array([1,2,3,4,5]), np.ones(3)/3, mode='valid')])"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {},
"outputs": [],
"source": [
"\n",
"def weights_generator(t, y_price, bs_vol, Growth_rate, Real_vol, ma_window): \n",
" \n",
" \"\"\"\n",
" Purpose: generate weights based on black-shoals option pricing model and observed price. \n",
" \n",
" Assumption: \n",
" 1. Rebalnce following a delta replicating strategy of a call option on Token / Collateral. \n",
" 2. Price percentage change (approximately equals to log(p(t+1)/p(t))) follows a linear growth treand \n",
" (slope = growth rate) plus a random walk Brownie motion. \n",
" \n",
" Input: \n",
" - t: term of the loan \n",
" - y_price: initial price y token (e.g. 1 btc = 50000 btc) \n",
" - bs_vol: black shoral option volatilty \n",
" - Growth_rate: linear growth slope of y price \n",
" - Real_vol: growth Brownie motion volatility \n",
" - ma_window: weights moving average window. \n",
" Pad zero if t < ma_window \n",
" Output: \n",
" - A dataframe with columns: ['t', 'y_price', 'weights_ytoken', 'slippage_rebalance'] \n",
" \n",
" \"\"\"\n",
" if set_random_seed == True:\n",
" np.random.seed(101)\n",
" # loan life duation between t(i) - t(i+1)\n",
" delta_t = t[:-1]-t[1:]\n",
" # token price percent change following a linear growth curve plus a random walk with volatility = Real_vol \n",
" rw = np.random.random(size = len(t)-1)\n",
"# delta_price = delta_t * (Growth_rate - 0.5*Real_vol**2) + Real_vol *np.sqrt(delta_t)*np.random.normal(0, 1, len(t)-1) #norm.ppf(rw)\n",
" delta_price = delta_t * (Growth_rate - 0.5*Real_vol**2) + Real_vol *np.sqrt(delta_t)*norm.ppf(rw)\n",
" \n",
" # calcualte price with exponetial of cumulative sum of log price differences. \n",
" price_logdiff= np.hstack([0, delta_price]).cumsum()\n",
" price = y_price*np.exp(price_logdiff)\n",
" # reblance weights by black scholes option equestion. \n",
" w_y = norm.cdf((price_logdiff[:-1] + t[:-1]*(0.5*bs_vol**2))/bs_vol/np.sqrt(t[:-1]))\n",
" \n",
" # set a boundary [1e-8, 1-1e-8] to avoid 0 as denominator \n",
" w_y[w_y< 1e-8] = 1e-8 \n",
" w_y[w_y> (1- 1e-8)] = 1- 1e-8\n",
" \n",
" weights_ytoken = np.append(w_y, w_y[-1])\n",
" assert isinstance(ma_window, int), 'Moving average window must be integer!' \n",
" if ma_window == 1:\n",
" _wt_ma = weights_ytoken.copy()\n",
" else:\n",
" _wt_ma = np.append(weights_ytoken[:ma_window-1], np.convolve(weights_ytoken, np.ones(ma_window)/ma_window, mode='valid'))\n",
" #_wt_ma = np.convolve(weights_ytoken, np.ones(ma_window)/ma_window, mode='same')\n",
" slippage = np.append([0], (_wt_ma[:-1]/_wt_ma[1:])**_wt_ma[1:]*( (1-_wt_ma[:-1])/(1-_wt_ma[1:]))**(1-_wt_ma[1:])-1)\n",
" # formate the output file as a dataframe for downstream analysis \n",
" _temp= np.column_stack((t, price, _wt_ma, slippage))\n",
" out_vec = pd.DataFrame(_temp, columns = ['t', 'y_price', 'weights_ytoken', 'slippage_rebalance'])\n",
" return out_vec \n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"class PoolEngine_v1: \n",
" \"\"\"\n",
" pool engine class\n",
" Reflect real senario when weight changes makes arbitrage oppurtunites and pool value loss \n",
" \n",
" Attribute: \n",
" - x: token 1 balance\n",
" - y: token 2 balance\n",
" - w_x: weight of x \n",
" - collateral value \n",
" - collateral with rebate \n",
" - fee\n",
" - rebate \n",
" \n",
" Method:\n",
" - sp(): spot price of one y token in terms of x token\n",
" - in-given-out(inTtype, amount): size of in given out amount \n",
" - out-given-in(inTtype, amount): size of our given in amount \n",
" - in_given_price(inTtype, p): size of in to bring spot price to p the price of out token as function of in token\n",
" \n",
" \"\"\"\n",
" def __init__(self, x, y, w_x):\n",
" assert 0<w_x<1 , \"weights must be in (0,1) !\"\n",
" self.x = x\n",
" self.y = y\n",
" self.w_x = w_x \n",
"\n",
" def sp(self):\n",
" w_y = 1- self.w_x\n",
" p = self.x*w_y/(self.y*self.w_x) \n",
" return p \n",
" \n",
" def in_given_out(self, inTtype, amount):\n",
" if inTtype == 'x':\n",
" assert 0<= amount < self.y, 'out amount must be greater than 0 and less than total'\n",
" ratio = (1-self.w_x)/self.w_x \n",
" Ain = self.x * ((self.y/(self.y -amount))**(ratio) -1)\n",
" else: \n",
" assert 0<= amount < self.x, 'out amount must be greater than 0 and less than total'\n",
" ratio = self.w_x/(1-self.w_x) \n",
" Ain = self.y * ((self.x/(self.x -amount))**(ratio) -1)\n",
" return Ain \n",
" \n",
" def out_given_in(self, inTtype, amount):\n",
" assert amount >= 0, 'amount must be greater than 0'\n",
" if inTtype == 'x':\n",
" ratio = self.w_x / (1-self.w_x)\n",
" Aout = self.y * (1- (self.x/(self.x+amount))**ratio) \n",
" else: \n",
" ratio = (1-self.w_x)/self.w_x \n",
" Aout = self.x * (1- (self.y/(self.y+amount))**ratio) \n",
" return Aout\n",
"\n",
" def in_given_price(self, inTtype, price): \n",
" assert price >0 , 'price must be greater than 0'\n",
" if inTtype == 'x':\n",
" sp_price = self.sp()\n",
" Ain = self.x * ((price/sp_price)**(1-self.w_x) - 1) \n",
" else: \n",
" sp_price = 1/self.sp()\n",
" Ain = self.y * ((price/sp_price)**self.w_x - 1) \n",
" return Ain \n",
" "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def get_episode_full(t,y_price_init, bs_vol, Growth_rate, Real_vol, Collateral, LTV0, fee_rate, rebate,\n",
" pool_init_x, pool_init_y, pool_init_wx, ma_window):\n",
" \n",
" \"\"\"\n",
" Purpuse: \n",
" generate one simulation based on selected parameters and random price change following the Browian motion. \n",
" pool attributes are rescorded at each step. \n",
" Rebate and fee values are back to the pool. \n",
" \n",
" Input: \n",
" - t: term of the loan (e.g. 90 days)\n",
" - y_price: initial price of loan token (e.g. USDC = 3e-5 btc) \n",
" - bs_vol: black shoral option volatilty \n",
" - Growth_rate: linear growth slope of y price \n",
" - Real_vol: growth Brownie motion volatility \n",
" - Collateral amount (e.g. 200 BTC) \n",
" - LTV0: Initial LTV (e.g. 90%)\n",
" - fee: e.g. 0.15% of absolute weight changes \n",
" - rebate: e.g. 50% slippage due to rebalance weigths \n",
" - pool_init_x: Initial x size \n",
" - pool_init_y: Initial y size \n",
" - Pool_init_wx: Inital x weight (e.g. 50%)\n",
" - ma_window: weights moving average window. \n",
" Pad zero if t < ma_window \n",
" \n",
" Output: \n",
" Dataframe with the following key measures. \n",
" \n",
" - 1. Token price at time t \n",
" - 2. Reblanced weight at time t \n",
" - 3. Daily pool value loss (relative to collaterals)\n",
" slippage due to rebalance weights \n",
" total changes due to rebalance weights and price changes \n",
" - 4. Collateral value \n",
" - 5. Collateral value with rebate \n",
" - 6. LTV relative to collateral\n",
" - 7. LTV relative to collateral with rebate \n",
"\n",
" \"\"\"\n",
" # this function replicate the CRP balance sheet. \n",
" \n",
"\n",
"\n",
" episode = weights_generator(t, y_price_init, bs_vol, Growth_rate, Real_vol, ma_window)\n",
" wt = episode.weights_ytoken\n",
" oracle_price = episode.y_price\n",
" oracle_price_ratio = np.append([1],oracle_price[1:].values/oracle_price[:-1].values) \n",
"\n",
" borrowed_usd = Collateral*LTV0\n",
" slippage_rebalance = episode.slippage_rebalance.values\n",
" fee = np.abs(episode.weights_ytoken.diff(1).fillna(0).values)*fee_rate\n",
" episode_output=[]\n",
" with_rebate=[]\n",
" slppage_trading=[]\n",
" #add pool rebalancing cost and trading cost \n",
" #Pool starts at time 0 and run through the whole life cicle \n",
" rbpool = PoolEngine_v1(x = pool_init_x, y = pool_init_y, w_x = pool_init_wx)\n",
"\n",
" for i in wt.index:\n",
" if i==0: \n",
" implied_p = rbpool.sp()\n",
" btc_bal = Collateral*wt[i]\n",
" usd_bal = Collateral*(1-wt[i])\n",
" collateral_value = btc_bal+usd_bal\n",
" coll_with_rebate = Collateral\n",
" splippage_check = 0 \n",
" rbpool.x = usd_bal \n",
" rbpool.y = btc_bal/oracle_price[i]\n",
" else: \n",
" rbpool.w_x = 1- wt[i]\n",
" implied_p = rbpool.sp()\n",
" delta_x = rbpool.in_given_price('x', oracle_price[i])\n",
" delta_y = rbpool.in_given_price('y', 1/oracle_price[i])\n",
" btc_bal = btc_bal/oracle_price[i-1]*oracle_price[i] + delta_y*oracle_price[i-1] \n",
" usd_bal = usd_bal + delta_x \n",
" splippage_check = (delta_y*oracle_price[i]+delta_x)/collateral_value\n",
" collateral_value = btc_bal+usd_bal\n",
" rbpool.x = usd_bal \n",
" rbpool.y = btc_bal/oracle_price[i]\n",
" coll_with_rebate = x_locked + y_locked/oracle_price[i-1]*oracle_price[i] \\\n",
" + coll_with_rebate*(slippage_rebalance[i] *(1-rebate)+fee[i]) \n",
" x_locked = coll_with_rebate*(1-wt[i])\n",
" y_locked = coll_with_rebate* wt[i]\n",
" with_rebate.append((x_locked, y_locked, coll_with_rebate, btc_bal, usd_bal, collateral_value, splippage_check)) \n",
"\n",
" _with_rebate = pd.DataFrame(np.array(with_rebate), columns= ['x_locked', 'y_locked', 'coll_with_rebate',\\\n",
" 'btc_bal', 'usd_bal', 'collateral_value', 'splippage_check'])\n",
" #collateral value in usd \n",
" episode_full = pd.concat([episode, _with_rebate], axis=1)\n",
" # collateral value with re \n",
" episode_full['ltv_rebalance'] = borrowed_usd/(episode_full['collateral_value'])\n",
" episode_full['ltv_with_rebate'] = borrowed_usd/episode_full['coll_with_rebate'] \n",
" episode_full['wt_chg'] = episode_full['weights_ytoken'].diff(1).fillna(0)\n",
" episode_full['pvtc_rebate'] = _with_rebate['coll_with_rebate'].values/episode_full['y_price'].values/(Collateral/y_price_init)\n",
"\n",
" return episode_full\n"
]
},
{
"cell_type": "code",
"execution_count": 42,
"metadata": {},
"outputs": [],
"source": [
"def episode_plot(var_list, set_random_seed = True, row=2, col=3, Real_vol = 0.5, Growth_rate = 1, LTV0 = 0.8, bs_vol = 0.5,\n",
" y_price_init = 50000, Collateral = 1000000, pool_init_x = 5000000, pool_init_y = 100, pool_init_wx = 0.5,\n",
" fee_rate = 0.0015, rebate=0.5, ma_window =7):\n",
" %matplotlib notebook\n",
" import matplotlib.pyplot as plt \n",
" fig, axs = plt.subplots(row,col,figsize=(8,6))\n",
" axs[-1, -1].axis('off')\n",
" # Create axes for sliders\n",
" ax_growth = fig.add_axes([0.73, 0.3, 0.2, 0.02])\n",
" ax_growth.spines['top'].set_visible(True)\n",
" ax_growth.spines['right'].set_visible(True)\n",
"\n",
" ax_real_vol = fig.add_axes([0.73, 0.26, 0.2, 0.02])\n",
" ax_real_vol.spines['top'].set_visible(True)\n",
" ax_real_vol.spines['right'].set_visible(True)\n",
"\n",
" ax_ltv = fig.add_axes([0.73, 0.22, 0.2, 0.02])\n",
" ax_ltv.spines['top'].set_visible(True)\n",
"\n",
" ax_bs_vol = fig.add_axes([0.73, 0.18, 0.2, 0.02])\n",
" ax_bs_vol.spines['top'].set_visible(True)\n",
" ax_bs_vol.spines['right'].set_visible(True)\n",
"\n",
" ax_rebate = fig.add_axes([0.73, 0.14, 0.2, 0.02])\n",
" ax_rebate.spines['top'].set_visible(True)\n",
" ax_rebate.spines['right'].set_visible(True)\n",
"\n",
" ax_ma = fig.add_axes([0.73, 0.10, 0.2, 0.02])\n",
" ax_ma.spines['top'].set_visible(True)\n",
" ax_ma.spines['right'].set_visible(True)\n",
" \n",
" ax_fee = fig.add_axes([0.73, 0.34, 0.2, 0.02])\n",
" ax_fee.spines['top'].set_visible(True)\n",
" ax_fee.spines['right'].set_visible(True)\n",
"\n",
" # Create sliders\n",
" s_real_vol= Slider(ax=ax_real_vol, label='Real Vol', valmin=0.1, valmax=1.0, valinit=0.5, valfmt=' %1.2f ', facecolor='#cc7000')\n",
" s_growth = Slider(ax=ax_growth, label='Growth', valmin=-2, valmax=2, valinit=1, valfmt=' %1.2f', facecolor='#cc7000')\n",
" s_bs_vol= Slider(ax=ax_bs_vol, label='BS Vol', valmin=0.1, valmax=1.0, valinit=0.5, valfmt=' %1.2f ', facecolor='#cc7000')\n",
" s_ltv = Slider(ax=ax_ltv, label='LTV0', valmin=0.7, valmax=0.95, valinit=0.8, valfmt=' %1.2f', facecolor='#cc7000')\n",
" s_rebate = Slider(ax=ax_rebate, label='Rebate', valmin=0.0, valmax=1, valinit=0, valfmt=' %1.2f', facecolor='#cc7000')\n",
" s_ma = Slider(ax=ax_ma, label='MA', valmin=1, valmax=30, valinit=20, valfmt=' %0.0f ', facecolor='#cc7000')\n",
" s_fee = Slider(ax=ax_fee, label='Fee', valmin=0.0, valmax=0.01, valinit=0.0015, valfmt=' %1.4f', facecolor='#cc7000')\n",
"\n",
"\n",
" # Plot default data\n",
" t = np.linspace(91,0,92)/365\n",
" _data = get_episode_full(t,y_price_init, bs_vol, Growth_rate, Real_vol, Collateral, LTV0, fee_rate, rebate,\n",
" pool_init_x, pool_init_y, pool_init_wx, ma_window)\n",
"\n",
" x = _data.index.values\n",
" y1 = _data[var_list[0]]/y_price_init\n",
" y2a = _data[var_list[1]]\n",
" y2b = 1-_data[var_list[1]] \n",
" y3 = _data[var_list[2]]\n",
" y4 = _data[var_list[3]]\n",
" y5 = _data[var_list[4]]\n",
" y6 = _data[var_list[5]]\n",
"\n",
" f_d1, = axs[0,0].plot(x, y1, linewidth=2.5)\n",
" f_d2a, = axs[0,1].plot(x, y2a, linewidth=2.5)\n",
" f_d2b, = axs[0,1].plot(x, y2b, linewidth=2.5)\n",
" f_d3, = axs[0,2].plot(x, y3, linewidth=2.5)\n",
" f_d4, = axs[1,0].plot(x, y4, linewidth=2.5)\n",
" f_d5, = axs[1,1].plot(y6, y5, marker= 'o', linestyle = 'None', linewidth=1)\n",
"\n",
"\n",
" axs[0,0].set_ylim(0.5,2.5)\n",
" axs[0,0].axhline(y = 1, color = 'r', linestyle = '--')\n",
" axs[0,1].set_ylim(-0.02,1.02)\n",
" axs[0,1].axhline(y = 0.5, color = 'r', linestyle = '--')\n",
" axs[0,2].set_ylim(0.5,2)\n",
" axs[0,2].axhline(y = 1, color = 'r', linestyle = '--')\n",
" axs[1,0].set_ylim(0.5,2)\n",
" axs[1,0].axhline(y = 1, color = 'r', linestyle = '--')\n",
" axs[1,1].set_ylim(-0.2,0.05)\n",
" axs[1,1].set_xlim(-0.5,0.5)\n",
" axs[1,1].axhline(y = 0, color = 'r', linestyle = '--')\n",
" \n",
" \n",
" axs[0,0].set_ylabel('BTC relative change ')\n",
" axs[0,1].set_ylabel('Weights (USDC: Orange, BTC: Blue)')\n",
" axs[0,2].set_ylabel('LTV-with rebate')\n",
" axs[1,0].set_ylabel('PVTC with rebate') \n",
" axs[1,1].set_ylabel('Impermanent Loss vs weight chg') \n",
"\n",
" for i in range(2):\n",
" for j in range(3):\n",
" axs[i,j].set_xlabel('Day')\n",
" axs[i,j].set_title('({})'.format(3*i+j+1))\n",
" axs[1,1].set_xlabel('weight changes')\n",
" # Update values\n",
" def update(val):\n",
" _real_vol = s_real_vol.val\n",
" _growth = s_growth.val\n",
" _bs_vol = s_bs_vol.val\n",
" _ltv = s_ltv.val\n",
" _rebate = s_rebate.val\n",
" _ma = int(s_ma.val)\n",
" _fee = s_fee.val\n",
" _data = get_episode_full(t,y_price_init, _bs_vol, _growth, _real_vol, Collateral, _ltv, _fee, _rebate,\n",
" pool_init_x, pool_init_y, pool_init_wx, _ma)\n",
" x = _data.index.values\n",
" _y1 = _data[var_list[0]]/y_price_init\n",
" _y2a = _data[var_list[1]]\n",
" _y2b = 1-_data[var_list[1]] \n",
" _y3 = _data[var_list[2]]\n",
" _y4 = _data[var_list[3]]\n",
" _y5 = _data[var_list[4]]\n",
" _y6 = _data[var_list[5]]\n",
"\n",
" f_d1.set_data(x,_y1)\n",
" f_d2a.set_data(x,_y2a)\n",
" f_d2b.set_data(x,_y2b)\n",
" f_d3.set_data(x,_y3)\n",
" f_d4.set_data(x,_y4)\n",
" f_d5.set_data(_y6,_y5)\n",
" fig.canvas.draw_idle()\n",
"\n",
" s_real_vol.on_changed(update)\n",
" s_growth.on_changed(update)\n",
" s_ltv.on_changed(update)\n",
" s_bs_vol.on_changed(update)\n",
" s_rebate.on_changed(update)\n",
" s_ma.on_changed(update)\n",
" s_fee.on_changed(update)\n",
" fig.tight_layout()\n",
" plt.show()\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.7.3"
}
},
"nbformat": 4,
"nbformat_minor": 4
}

116247
patch Normal file

File diff suppressed because one or more lines are too long