Update HC for Margin trade and HLS (#645)

* Update HC for Margin trade and HLS

* fix relative imports
This commit is contained in:
Bob van der Helm 2023-11-20 12:25:16 +01:00 committed by GitHub
parent 046ec1c4cd
commit 9a05d03db5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 157 additions and 34 deletions

View File

@ -12,7 +12,7 @@ export default function MaxLeverage(props: Props) {
return (
<FormattedNumber
amount={props.strategy.maxLeverage}
options={{ minDecimals: 2, maxDecimals: 2, suffix: 'x' }}
options={{ minDecimals: 2, maxDecimals: 2, suffix: 'x', prefix: '~' }}
className='text-xs'
animate
/>

View File

@ -2,9 +2,12 @@ import { useCallback, useMemo } from 'react'
import useDepositHlsVault from 'hooks/useDepositHlsVault'
import useHealthComputer from 'hooks/useHealthComputer'
import useSwapValueLoss from 'hooks/useSwapValueLoss'
import { useUpdatedAccount } from 'hooks/useUpdatedAccount'
import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin'
import { SWAP_FEE_BUFFER } from 'utils/constants'
import { BN } from 'utils/helpers'
interface Props {
borrowAsset: Asset
@ -16,6 +19,10 @@ export default function useStakingController(props: Props) {
const { collateralAsset, borrowAsset, selectedAccount } = props
const addToStakingStrategy = useStore((s) => s.addToStakingStrategy)
const { data: swapValueLoss } = useSwapValueLoss(
props.borrowAsset.denom,
props.collateralAsset.denom,
)
const {
leverage,
setDepositAmount,
@ -33,9 +40,13 @@ export default function useStakingController(props: Props) {
const { computeMaxBorrowAmount } = useHealthComputer(updatedAccount)
const maxBorrowAmount = useMemo(() => {
// TODO: Perhaps we need a specific target for this -> target = swap
return computeMaxBorrowAmount(props.borrowAsset.denom, 'deposit')
}, [computeMaxBorrowAmount, props.borrowAsset.denom])
return computeMaxBorrowAmount(props.borrowAsset.denom, {
swap: {
denom_out: props.collateralAsset.denom,
slippage: BN(swapValueLoss).plus(SWAP_FEE_BUFFER).toString(),
},
})
}, [computeMaxBorrowAmount, props.borrowAsset.denom, props.collateralAsset.denom, swapValueLoss])
const execute = useCallback(() => {
useStore.setState({ hlsModal: null })

View File

@ -15,6 +15,7 @@ import { BNCoin } from 'types/classes/BNCoin'
import { getAccountPositionValues } from 'utils/accounts'
import { getHlsStakingChangeLevActions } from 'utils/actions'
import { byDenom } from 'utils/array'
import { SWAP_FEE_BUFFER } from 'utils/constants'
import { getLeveragedApy } from 'utils/math'
import { getDepositCapMessage, getHealthFactorMessage, getLiquidityMessage } from 'utils/messages'
@ -45,8 +46,13 @@ export default function ChangeLeverage(props: Props) {
const [currentDebt, setAmount] = useState(previousDebt)
const maxBorrowAmount = useMemo(() => {
return computeMaxBorrowAmount(props.borrowAsset.denom, 'deposit').plus(previousDebt)
}, [computeMaxBorrowAmount, previousDebt, props.borrowAsset.denom])
return computeMaxBorrowAmount(props.borrowAsset.denom, {
swap: {
denom_out: props.collateralAsset.denom,
slippage: SWAP_FEE_BUFFER.toString(),
},
}).plus(previousDebt)
}, [computeMaxBorrowAmount, previousDebt, props.borrowAsset.denom, props.collateralAsset.denom])
const onChangeAmount = useCallback(
(currentDebt: BigNumber) => {

View File

@ -14,7 +14,7 @@ import { AvailableOrderType } from 'components/Trade/TradeModule/SwapForm/OrderT
import TradeSummary from 'components/Trade/TradeModule/SwapForm/TradeSummary'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { LocalStorageKeys } from 'constants/localStorageKeys'
import { BN_ZERO, MARGIN_TRADE_BUFFER } from 'constants/math'
import { BN_ZERO } from 'constants/math'
import useAutoLend from 'hooks/useAutoLend'
import useCurrentAccount from 'hooks/useCurrentAccount'
import useHealthComputer from 'hooks/useHealthComputer'
@ -114,9 +114,11 @@ export default function SwapForm(props: Props) {
const [maxSellAmount, sellSideMarginThreshold, marginRatio] = useMemo(() => {
const maxAmount = computeMaxSwapAmount(sellAsset.denom, buyAsset.denom, 'default')
const maxAmountOnMargin = computeMaxSwapAmount(sellAsset.denom, buyAsset.denom, 'margin')
.multipliedBy(MARGIN_TRADE_BUFFER)
.integerValue()
const maxAmountOnMargin = computeMaxSwapAmount(
sellAsset.denom,
buyAsset.denom,
'margin',
).integerValue()
const marginRatio = maxAmount.dividedBy(maxAmountOnMargin)
estimateExactIn(

View File

@ -3,7 +3,6 @@ import { BN } from 'utils/helpers'
export const BN_ZERO = BN(0)
export const BN_ONE = BN(1)
export const MARGIN_TRADE_BUFFER = 0.9
export const MIN_AMOUNT = 0.000001
export const MAX_AMOUNT_DECIMALS = 6

View File

@ -1,3 +1,4 @@
import BigNumber from 'bignumber.js'
import { useMemo } from 'react'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
@ -5,8 +6,10 @@ import { LocalStorageKeys } from 'constants/localStorageKeys'
import { BN_ZERO } from 'constants/math'
import useLocalStorage from 'hooks/useLocalStorage'
import usePrices from 'hooks/usePrices'
import useSwapValueLoss from 'hooks/useSwapValueLoss'
import { BNCoin } from 'types/classes/BNCoin'
import { Action } from 'types/generated/mars-credit-manager/MarsCreditManager.types'
import { SWAP_FEE_BUFFER } from 'utils/constants'
import { getCoinAmount, getCoinValue } from 'utils/formatters'
interface Props {
@ -16,9 +19,9 @@ interface Props {
export default function UseClosePositionActions(props: Props): Action[] {
const [slippage] = useLocalStorage<number>(LocalStorageKeys.SLIPPAGE, DEFAULT_SETTINGS.slippage)
const { data: prices } = usePrices()
const collateralDenom = props.account.strategy.denoms.deposit
const borrowDenom = props.account.strategy.denoms.borrow
const { data: swapValueLoss } = useSwapValueLoss(collateralDenom, borrowDenom)
const debtAmount: BigNumber = useMemo(
() =>
@ -27,12 +30,23 @@ export default function UseClosePositionActions(props: Props): Action[] {
[props.account.debts, props.account.strategy.denoms.borrow],
)
const collateralAmount: BigNumber = useMemo(
() =>
props.account.deposits.find(
(deposit) => deposit.denom === props.account.strategy.denoms.deposit,
)?.amount || BN_ZERO,
[props.account.deposits, props.account.strategy.denoms.deposit],
)
const swapInAmount = useMemo(() => {
const targetValue = getCoinValue(BNCoin.fromDenomAndBigNumber(borrowDenom, debtAmount), prices)
return getCoinAmount(collateralDenom, targetValue, prices)
.times(1 + slippage)
.integerValue()
}, [slippage, borrowDenom, debtAmount, prices, collateralDenom])
return BigNumber.max(
getCoinAmount(collateralDenom, targetValue, prices)
.times(1 + slippage + SWAP_FEE_BUFFER + swapValueLoss)
.integerValue(),
collateralAmount,
)
}, [borrowDenom, debtAmount, prices, slippage, swapValueLoss, collateralDenom, collateralAmount])
return useMemo<Action[]>(
() => [
@ -48,7 +62,10 @@ export default function UseClosePositionActions(props: Props): Action[] {
},
{
repay: {
coin: BNCoin.fromDenomAndBigNumber(borrowDenom, debtAmount).toActionCoin(),
coin: BNCoin.fromDenomAndBigNumber(
borrowDenom,
debtAmount.times(1.0001).integerValue(), // Over pay to by-pass increase in debt
).toActionCoin(),
},
},
]),

View File

@ -5,8 +5,10 @@ import { LocalStorageKeys } from 'constants/localStorageKeys'
import { BN_ZERO } from 'constants/math'
import useLocalStorage from 'hooks/useLocalStorage'
import usePrices from 'hooks/usePrices'
import useSwapValueLoss from 'hooks/useSwapValueLoss'
import { BNCoin } from 'types/classes/BNCoin'
import { Action } from 'types/generated/mars-credit-manager/MarsCreditManager.types'
import { SWAP_FEE_BUFFER } from 'utils/constants'
import { getCoinValue } from 'utils/formatters'
interface Props {
@ -17,6 +19,8 @@ export default function useDepositHlsVault(props: Props) {
const { data: prices } = usePrices()
const [slippage] = useLocalStorage<number>(LocalStorageKeys.SLIPPAGE, DEFAULT_SETTINGS.slippage)
const { data: valueLossPercentage } = useSwapValueLoss(props.borrowDenom, props.collateralDenom)
const [depositAmount, setDepositAmount] = useState<BigNumber>(BN_ZERO)
const [borrowAmount, setBorrowAmount] = useState<BigNumber>(BN_ZERO)
@ -42,9 +46,24 @@ export default function useDepositHlsVault(props: Props) {
return {
positionValue: collateralValue.plus(borrowValue),
leverage: borrowValue.dividedBy(collateralValue).plus(1).toNumber() || 1,
leverage:
borrowValue
.dividedBy(
collateralValue
.plus(borrowValue.times(1 - valueLossPercentage - SWAP_FEE_BUFFER))
.minus(borrowValue),
)
.plus(1)
.toNumber() || 1,
}
}, [borrowAmount, depositAmount, prices, props.collateralDenom, props.borrowDenom])
}, [
props.collateralDenom,
props.borrowDenom,
depositAmount,
prices,
borrowAmount,
valueLossPercentage,
])
const actions: Action[] = useMemo(
() => [

View File

@ -1,7 +1,10 @@
import { useCallback, useEffect, useMemo, useState } from 'react'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { LocalStorageKeys } from 'constants/localStorageKeys'
import { BN_ZERO } from 'constants/math'
import useAssetParams from 'hooks/useAssetParams'
import useLocalStorage from 'hooks/useLocalStorage'
import usePrices from 'hooks/usePrices'
import useVaultConfigs from 'hooks/useVaultConfigs'
import {
@ -15,7 +18,7 @@ import {
} from 'types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types'
import { convertAccountToPositions } from 'utils/accounts'
import { getAssetByDenom } from 'utils/assets'
import { LTV_BUFFER } from 'utils/constants'
import { SWAP_FEE_BUFFER } from 'utils/constants'
import {
BorrowTarget,
compute_health_js,
@ -24,7 +27,7 @@ import {
max_withdraw_estimate_js,
SwapKind,
} from 'utils/health_computer'
import { BN } from 'utils/helpers' // Pyth returns prices with up to 32 decimals. Javascript only supports 18 decimals. So we need to scale by 14 t
import { BN } from 'utils/helpers'
// Pyth returns prices with up to 32 decimals. Javascript only supports 18 decimals. So we need to scale by 14 t
// avoid "too many decimals" errors.
@ -34,6 +37,7 @@ export default function useHealthComputer(account?: Account) {
const { data: prices } = usePrices()
const { data: assetParams } = useAssetParams()
const { data: vaultConfigs } = useVaultConfigs()
const [slippage] = useLocalStorage<number>(LocalStorageKeys.SLIPPAGE, DEFAULT_SETTINGS.slippage)
const [healthFactor, setHealthFactor] = useState(0)
const positions: Positions | null = useMemo(() => {
@ -147,9 +151,7 @@ export default function useHealthComputer(account?: Account) {
(denom: string, target: BorrowTarget) => {
if (!healthComputer) return BN_ZERO
try {
return BN(max_borrow_estimate_js(healthComputer, denom, target))
.multipliedBy(LTV_BUFFER)
.integerValue()
return BN(max_borrow_estimate_js(healthComputer, denom, target)).integerValue()
} catch (err) {
console.error(err)
return BN_ZERO
@ -175,12 +177,20 @@ export default function useHealthComputer(account?: Account) {
(from: string, to: string, kind: SwapKind) => {
if (!healthComputer) return BN_ZERO
try {
return BN(max_swap_estimate_js(healthComputer, from, to, kind))
return BN(
max_swap_estimate_js(
healthComputer,
from,
to,
kind,
BN(slippage).plus(SWAP_FEE_BUFFER).toString(),
),
)
} catch {
return BN_ZERO
}
},
[healthComputer],
[healthComputer, slippage],
)
const health = useMemo(() => {
const convertedHealth = BN(Math.log(healthFactor))

View File

@ -1,7 +1,8 @@
import { BN_ZERO } from 'constants/math'
import usePrices from 'hooks/usePrices'
export default function usePrice(denom: string) {
const { data: prices } = usePrices()
return prices.find((coin) => coin.denom === denom)?.amount ?? 0
return prices.find((coin) => coin.denom === denom)?.amount ?? BN_ZERO
}

View File

@ -0,0 +1,35 @@
import BigNumber from 'bignumber.js'
import useSWR from 'swr'
import estimateExactIn from 'api/swap/estimateExactIn'
import usePrice from 'hooks/usePrice'
import { BNCoin } from 'types/classes/BNCoin'
import { SWAP_FEE_BUFFER } from 'utils/constants'
import { BN } from 'utils/helpers'
const amountIn = 1000_000_000
export default function useSwapValueLoss(denomIn: string, denomOut: string) {
const denomInPrice = usePrice(denomIn)
const denomOutPrice = usePrice(denomOut)
return useSWR(
`swapValueLoss/${denomIn}/${denomOut}`,
async () => {
const valueIn = denomInPrice.times(amountIn)
const amountOut = await estimateExactIn(
BNCoin.fromDenomAndBigNumber(denomIn, BN(amountIn)).toCoin(),
denomOut,
)
const valueOut = denomOutPrice.times(amountOut)
return BigNumber.max(
valueIn.minus(valueOut).dividedBy(valueIn).decimalPlaces(6),
0,
).toNumber()
},
{
fallbackData: SWAP_FEE_BUFFER,
},
)
}

View File

@ -16,6 +16,7 @@ import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin'
import { calculateAccountLeverage, cloneAccount } from 'utils/accounts'
import { byDenom } from 'utils/array'
import { SWAP_FEE_BUFFER } from 'utils/constants'
import { getCoinAmount, getCoinValue } from 'utils/formatters'
import { getValueFromBNCoins } from 'utils/helpers'
@ -184,6 +185,8 @@ export function useUpdatedAccount(account?: Account) {
const additionalDebtValue = getCoinValue(borrowCoin, prices)
const tradeOutputAmount = getCoinAmount(depositCoin.denom, additionalDebtValue, prices)
.times(1 - SWAP_FEE_BUFFER)
.integerValue()
addTrades([BNCoin.fromDenomAndBigNumber(depositCoin.denom, tradeOutputAmount)])
},
[prices],
@ -193,6 +196,8 @@ export function useUpdatedAccount(account?: Account) {
(collateralDenom: string, debtDenom: string, repayAmount: BigNumber) => {
const repayValue = getCoinValue(BNCoin.fromDenomAndBigNumber(debtDenom, repayAmount), prices)
const removeDepositAmount = getCoinAmount(collateralDenom, repayValue, prices)
.times(1 + slippage)
.integerValue()
removeDeposits([BNCoin.fromDenomAndBigNumber(collateralDenom, removeDepositAmount)])
removeDebts([BNCoin.fromDenomAndBigNumber(debtDenom, repayAmount)])
},

View File

@ -9,12 +9,9 @@ export const defaultFee: StdFee = {
}
export const SECONDS_IN_A_YEAR = 31540000
export const LTV_BUFFER = 0.99
export const DEPOSIT_CAP_BUFFER = 0.999
export const VAULT_DEPOSIT_BUFFER = 0.999
export const SWAP_FEE_BUFFER = 0.005
export const DEFAULT_PORTFOLIO_STATS = [
{ title: null, sub: 'Total Balance' },
{ title: null, sub: 'Total Debt' },
@ -24,3 +21,4 @@ export const DEFAULT_PORTFOLIO_STATS = [
]
export const ENABLE_HLS = false
export const ENABLE_PERPS = false

View File

@ -27,6 +27,7 @@ export function max_borrow_estimate_js(
* @param {string} from_denom
* @param {string} to_denom
* @param {SwapKind} kind
* @param {Slippage} slippage
* @returns {string}
*/
export function max_swap_estimate_js(
@ -34,6 +35,7 @@ export function max_swap_estimate_js(
from_denom: string,
to_denom: string,
kind: SwapKind,
slippage: Slippage,
): string
export interface HealthComputer {
kind: AccountKind
@ -53,9 +55,15 @@ export interface HealthValuesResponse {
above_max_ltv: boolean
}
export type Slippage = Decimal
export type SwapKind = 'default' | 'margin'
export type BorrowTarget = 'deposit' | 'wallet' | { vault: { address: Addr } }
export type BorrowTarget =
| 'deposit'
| 'wallet'
| { vault: { address: Addr } }
| { swap: { denom_out: string; slippage: Decimal } }
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module
@ -72,6 +80,7 @@ export interface InitOutput {
e: number,
f: number,
g: number,
h: number,
) => void
readonly allocate: (a: number) => number
readonly deallocate: (a: number) => void

View File

@ -197,9 +197,10 @@ export function max_borrow_estimate_js(c, borrow_denom, target) {
* @param {string} from_denom
* @param {string} to_denom
* @param {SwapKind} kind
* @param {Slippage} slippage
* @returns {string}
*/
export function max_swap_estimate_js(c, from_denom, to_denom, kind) {
export function max_swap_estimate_js(c, from_denom, to_denom, kind, slippage) {
let deferred3_0
let deferred3_1
try {
@ -208,7 +209,16 @@ export function max_swap_estimate_js(c, from_denom, to_denom, kind) {
const len0 = WASM_VECTOR_LEN
const ptr1 = passStringToWasm0(to_denom, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc)
const len1 = WASM_VECTOR_LEN
wasm.max_swap_estimate_js(retptr, addHeapObject(c), ptr0, len0, ptr1, len1, addHeapObject(kind))
wasm.max_swap_estimate_js(
retptr,
addHeapObject(c),
ptr0,
len0,
ptr1,
len1,
addHeapObject(kind),
addHeapObject(slippage),
)
var r0 = getInt32Memory0()[retptr / 4 + 0]
var r1 = getInt32Memory0()[retptr / 4 + 1]
deferred3_0 = r0

View File

@ -12,6 +12,7 @@ export function max_swap_estimate_js(
e: number,
f: number,
g: number,
h: number,
): void
export function allocate(a: number): number
export function deallocate(a: number): void