feat: Some refactoring - now it all works and is hooked up. Still lots of nice formatting but at least swap is now providing leverage validaiton.

This commit is contained in:
Mr. Dummy Tester 2020-12-25 14:39:06 -06:00
parent 0b0e5773c2
commit fc23f18420
8 changed files with 91 additions and 70 deletions

View File

@ -14,10 +14,11 @@ export class NumericInput extends React.Component<any, any> {
onBlur = () => { onBlur = () => {
const { value, onBlur, onChange } = this.props; const { value, onBlur, onChange } = this.props;
let valueTemp = value; let valueTemp = value;
if (value.charAt(value.length - 1) === '.' || value === '-') { if (value === undefined || value === null) return;
if (value.charAt && (value.charAt(value.length - 1) === '.' || value === '-')) {
valueTemp = value.slice(0, -1); valueTemp = value.slice(0, -1);
} }
if (value.startsWith('.') || value.startsWith('-.')) { if (value.startsWith && (value.startsWith('.') || value.startsWith('-.'))) {
valueTemp = valueTemp.replace('.', '0.'); valueTemp = valueTemp.replace('.', '0.');
} }
onChange?.(valueTemp.replace(/0*(\d+)/, '$1')); onChange?.(valueTemp.replace(/0*(\d+)/, '$1'));

View File

@ -66,4 +66,5 @@ export const LABELS = {
NOT_ENOUGH_MARGIN_MESSAGE: 'Not enough buying power in oyster to make this trade at this leverage.', NOT_ENOUGH_MARGIN_MESSAGE: 'Not enough buying power in oyster to make this trade at this leverage.',
LEVERAGE_LIMIT_MESSAGE: LEVERAGE_LIMIT_MESSAGE:
'With liquidity pools in their current state, you are not allowed to use leverage at this multiple. You will need more margin to make this trade.', 'With liquidity pools in their current state, you are not allowed to use leverage at this multiple. You will need more margin to make this trade.',
NO_DEPOSIT_MESSAGE: 'You need to deposit coin of this type into oyster before trading with it on margin.',
}; };

View File

@ -178,6 +178,7 @@ export const collateralExchangeRate = (reserve?: LendingReserve) => {
export const collateralToLiquidity = (collateralAmount: BN | number, reserve?: LendingReserve) => { export const collateralToLiquidity = (collateralAmount: BN | number, reserve?: LendingReserve) => {
const amount = typeof collateralAmount === 'number' ? collateralAmount : collateralAmount.toNumber(); const amount = typeof collateralAmount === 'number' ? collateralAmount : collateralAmount.toNumber();
console.log('Exchange rate:', collateralExchangeRate(reserve));
return Math.floor(amount / collateralExchangeRate(reserve)); return Math.floor(amount / collateralExchangeRate(reserve));
}; };

View File

@ -5,8 +5,8 @@ import { ArrowUpOutlined, ArrowDownOutlined } from '@ant-design/icons';
import tokens from '../../../config/tokens.json'; import tokens from '../../../config/tokens.json';
export function Breakdown({ item }: { item: Position }) { export function Breakdown({ item }: { item: Position }) {
let myPart = (item.asset?.value || 0) / item.leverage; let myPart = parseFloat(item.asset?.value || '0') / item.leverage;
const brokeragePart = (item.asset?.value || 0) - myPart; const brokeragePart = parseFloat(item.asset?.value || '0') - myPart;
const brokerageColor = 'brown'; const brokerageColor = 'brown';
const myColor = 'blue'; const myColor = 'blue';
const gains = 'green'; const gains = 'green';

View File

@ -1,17 +1,16 @@
import { Button, Card, Radio } from 'antd'; import { Button, Card, Radio } from 'antd';
import React, { useEffect, useMemo, useState } from 'react'; import React, { useState } from 'react';
import { ActionConfirmation } from '../../../components/ActionConfirmation'; import { ActionConfirmation } from '../../../components/ActionConfirmation';
import { NumericInput } from '../../../components/Input/numeric'; import { NumericInput } from '../../../components/Input/numeric';
import { TokenIcon } from '../../../components/TokenIcon'; import { TokenIcon } from '../../../components/TokenIcon';
import { LABELS } from '../../../constants'; import { LABELS } from '../../../constants';
import { cache, ParsedAccount } from '../../../contexts/accounts'; import { cache, ParsedAccount } from '../../../contexts/accounts';
import { collateralToLiquidity, LendingReserve, LendingReserveParser } from '../../../models/lending/reserve'; import { LendingReserve, LendingReserveParser } from '../../../models/lending/reserve';
import { Position } from './interfaces'; import { Position } from './interfaces';
import tokens from '../../../config/tokens.json'; import tokens from '../../../config/tokens.json';
import { CollateralSelector } from '../../../components/CollateralSelector'; import { CollateralSelector } from '../../../components/CollateralSelector';
import { Breakdown } from './Breakdown'; import { Breakdown } from './Breakdown';
import { usePoolForBasket } from '../../../utils/pools'; import { useLeverage } from './leverage';
import { useEnrichedPools } from '../../../contexts/market';
interface NewPositionFormProps { interface NewPositionFormProps {
lendingReserve: ParsedAccount<LendingReserve>; lendingReserve: ParsedAccount<LendingReserve>;
@ -29,64 +28,7 @@ export default function NewPositionForm({ lendingReserve, newPosition, setNewPos
}; };
const [showConfirmation, setShowConfirmation] = useState(false); const [showConfirmation, setShowConfirmation] = useState(false);
const collType = newPosition.collateral; useLeverage({ newPosition, setNewPosition });
const desiredType = newPosition.asset.type;
const pool = usePoolForBasket([
collType?.info?.liquidityMint?.toBase58(),
desiredType?.info?.liquidityMint?.toBase58(),
]);
const enriched = useEnrichedPools(pool ? [pool] : []);
// Leverage validation - if you choose this leverage, is it allowable, with your buying power and with
// the pool we have to cover you?
useEffect(() => {
if (!collType || !desiredType || !newPosition.asset.value || !enriched || enriched.length == 0) {
return;
}
const amountDesiredToPurchase = newPosition.asset.value;
const leverageDesired = newPosition.leverage;
console.log('collateral reserve', collType);
const amountAvailableInOysterForMargin = collateralToLiquidity(collType.info.availableLiquidity, desiredType.info);
const amountToDepositOnMargin = amountDesiredToPurchase / leverageDesired;
console.log(
'Amount desired',
amountDesiredToPurchase,
'leverage',
leverageDesired,
'amountAvailable',
amountAvailableInOysterForMargin,
' amount to deposit on margin',
amountToDepositOnMargin
);
if (amountToDepositOnMargin > amountAvailableInOysterForMargin) {
setNewPosition({ ...newPosition, error: LABELS.NOT_ENOUGH_MARGIN_MESSAGE });
return;
}
const liqA = enriched[0].liquidityA;
const liqB = enriched[0].liquidityB;
const supplyRatio = liqA / liqB;
console.log('Liq A', liqA, 'liq b', liqB, 'supply ratio', supplyRatio);
// change in liquidity is amount desired (in units of B) converted to collateral units(A)
const chgLiqA = collateralToLiquidity(amountDesiredToPurchase, collType.info);
const newLiqA = liqA - chgLiqA;
const newLiqB = liqB + amountDesiredToPurchase;
const newSupplyRatio = newLiqA / newLiqB; // 75 / 100
console.log('chg in liq a', chgLiqA, 'new liq a', newLiqA, 'new supply ratio', newSupplyRatio);
const priceImpact = Math.abs(100 - 100 * (newSupplyRatio / supplyRatio)); // abs(100 - 100*(0.75 / 1)) = 25%
const marginToLeverage = 100 / leverageDesired;
console.log('priceImpact', priceImpact, 'marginToLeverage', marginToLeverage);
if (marginToLeverage > priceImpact) {
// if their marginToLeverage ratio < priceImpact, we say hey ho no go
setNewPosition({ ...newPosition, error: LABELS.LEVERAGE_LIMIT_MESSAGE });
return;
}
}, [collType, desiredType, newPosition.asset.value, newPosition.leverage, enriched]);
return ( return (
<Card className='new-position-item new-position-item-left' bodyStyle={bodyStyle}> <Card className='new-position-item new-position-item-left' bodyStyle={bodyStyle}>
@ -121,8 +63,11 @@ export default function NewPositionForm({ lendingReserve, newPosition, setNewPos
borderColor: 'transparent', borderColor: 'transparent',
outline: 'transparent', outline: 'transparent',
}} }}
onChange={(v: number) => { onChange={(v: string) => {
setNewPosition({ ...newPosition, asset: { ...newPosition.asset, value: v } }); setNewPosition({
...newPosition,
asset: { ...newPosition.asset, value: v },
});
}} }}
placeholder='0.00' placeholder='0.00'
/> />

View File

@ -15,7 +15,7 @@ export const NewPosition = () => {
const [newPosition, setNewPosition] = useState<Position>({ const [newPosition, setNewPosition] = useState<Position>({
id: null, id: null,
leverage: 1, leverage: 1,
asset: { value: 0 }, asset: { value: '0' },
}); });
if (!lendingReserve) { if (!lendingReserve) {

View File

@ -13,7 +13,7 @@ export interface Position {
collateral?: ParsedAccount<LendingReserve>; collateral?: ParsedAccount<LendingReserve>;
asset: { asset: {
type?: ParsedAccount<LendingReserve>; type?: ParsedAccount<LendingReserve>;
value: number; value: string; // because NumericInput returns strings and I dont want to deal with fixing it right now
}; };
error?: string; error?: string;
} }

View File

@ -0,0 +1,73 @@
import { useEffect } from 'react';
import { LABELS } from '../../../constants';
import { useEnrichedPools } from '../../../contexts/market';
import { useUserDeposits } from '../../../hooks';
import { collateralToLiquidity, liquidityToCollateral } from '../../../models';
import { usePoolForBasket } from '../../../utils/pools';
import { Position } from './interfaces';
export function useLeverage({
newPosition,
setNewPosition,
}: {
newPosition: Position;
setNewPosition: (pos: Position) => void;
}) {
const collType = newPosition.collateral;
const desiredType = newPosition.asset.type;
const pool = usePoolForBasket([
collType?.info?.liquidityMint?.toBase58(),
desiredType?.info?.liquidityMint?.toBase58(),
]);
const userDeposits = useUserDeposits();
const collateralDeposit = userDeposits.userDeposits.find(
(u) => u.reserve.info.liquidityMint.toBase58() == collType?.info?.liquidityMint?.toBase58()
);
const enriched = useEnrichedPools(pool ? [pool] : []);
// Leverage validation - if you choose this leverage, is it allowable, with your buying power and with
// the pool we have to cover you?
useEffect(() => {
if (!collateralDeposit) {
setNewPosition({ ...newPosition, error: LABELS.NO_DEPOSIT_MESSAGE });
return;
}
if (!collType || !desiredType || !newPosition.asset.value || !enriched || enriched.length == 0) {
return;
}
const amountDesiredToPurchase = parseFloat(newPosition.asset.value);
const leverageDesired = newPosition.leverage;
const amountAvailableInOysterForMargin = collateralToLiquidity(collateralDeposit.info.amount, desiredType.info);
const amountToDepositOnMargin = amountDesiredToPurchase / leverageDesired;
if (amountToDepositOnMargin > amountAvailableInOysterForMargin) {
setNewPosition({ ...newPosition, error: LABELS.NOT_ENOUGH_MARGIN_MESSAGE });
return;
}
const liqA = enriched[0].liquidityA;
const liqB = enriched[0].liquidityB;
const supplyRatio = liqA / liqB;
// change in liquidity is amount desired (in units of B) converted to collateral units(A)
const chgLiqA = liquidityToCollateral(amountDesiredToPurchase, desiredType.info);
const newLiqA = liqA - chgLiqA;
const newLiqB = liqB + amountDesiredToPurchase;
const newSupplyRatio = newLiqA / newLiqB;
const priceImpact = Math.abs(100 - 100 * (newSupplyRatio / supplyRatio));
const marginToLeverage = 100 / leverageDesired; // Would be 20% for 5x
if (marginToLeverage < priceImpact && leverageDesired != 1) {
// Obviously we allow 1x as edge case
// if their marginToLeverage ratio < priceImpact, we say hey ho no go
setNewPosition({ ...newPosition, error: LABELS.LEVERAGE_LIMIT_MESSAGE });
return;
}
setNewPosition({ ...newPosition, error: '' });
}, [collType, desiredType, newPosition.asset.value, newPosition.leverage, enriched]);
}