Merge branch 'main' into edit-orders

This commit is contained in:
Adrian Brzeziński 2022-12-15 02:08:17 +01:00
commit 0ebad8d9a3
14 changed files with 186 additions and 122 deletions

View File

@ -2,7 +2,6 @@ import { Bank, Serum3Market } from '@blockworks-foundation/mango-v4'
import useJupiterMints from 'hooks/useJupiterMints'
import { QuestionMarkCircleIcon } from '@heroicons/react/20/solid'
import mangoStore from '@store/mangoStore'
import Decimal from 'decimal.js'
import useMangoAccount from 'hooks/useMangoAccount'
import { useViewport } from 'hooks/useViewport'
import { useTranslation } from 'next-i18next'
@ -17,7 +16,7 @@ import {
trimDecimals,
} from 'utils/numbers'
import { breakpoints } from 'utils/theme'
import { calculateMarketPrice } from 'utils/tradeForm'
import { calculateLimitPriceForMarketOrder } from 'utils/tradeForm'
import { LinkButton } from './Button'
import { Table, Td, Th, TrBody, TrHead } from './TableElements'
import useSelectedMarket from 'hooks/useSelectedMarket'
@ -218,9 +217,9 @@ const Balance = ({ bank }: { bank: Bank }) => {
(balance > 0 && type === 'quote') || (balance < 0 && type === 'base')
? 'buy'
: 'sell'
price = calculateMarketPrice(orderbook, balance, side)
price = calculateLimitPriceForMarketOrder(orderbook, balance, side)
} else {
price = new Decimal(tradeForm.price).toNumber()
price = Number(tradeForm.price)
}
let minOrderDecimals: number

View File

@ -61,6 +61,16 @@ export const getTokenInMax = (
inputBank.mintDecimals
)
console.log(
'getMaxSourceUiForTokenSwap',
mangoAccount.getMaxSourceUiForTokenSwap(
group,
inputBank.mint,
outputBank.mint,
inputBank.uiPrice / outputBank.uiPrice
)
)
const inputBankVaultBalance = floorToDecimal(
group.getTokenVaultBalanceByMintUi(inputBank.mint),
inputBank.mintDecimals

View File

@ -22,7 +22,7 @@ import NumberFormat, {
} from 'react-number-format'
import { notify } from 'utils/notifications'
import SpotSlider from './SpotSlider'
import { calculateMarketPrice } from 'utils/tradeForm'
import { calculateLimitPriceForMarketOrder } from 'utils/tradeForm'
import Image from 'next/legacy/image'
import { QuestionMarkCircleIcon } from '@heroicons/react/20/solid'
import Loading from '@components/shared/Loading'
@ -45,12 +45,13 @@ const TABS: [string, number][] = [
['Market', 0],
]
const set = mangoStore.getState().set
const AdvancedTradeForm = () => {
const { t } = useTranslation(['common', 'trade'])
const set = mangoStore.getState().set
const tradeForm = mangoStore((s) => s.tradeForm)
const { mangoTokens } = useJupiterMints()
const { selectedMarket, price: marketPrice } = useSelectedMarket()
const { selectedMarket, price: oraclePrice } = useSelectedMarket()
const [useMargin, setUseMargin] = useState(true)
const [placingOrder, setPlacingOrder] = useState(false)
const [tradeFormSizeUi] = useLocalStorageState(SIZE_INPUT_UI_KEY, 'Slider')
@ -93,14 +94,11 @@ const AdvancedTradeForm = () => {
return ''
}, [quoteSymbol, mangoTokens])
const setTradeType = useCallback(
(tradeType: 'Limit' | 'Market') => {
set((s) => {
s.tradeForm.tradeType = tradeType
})
},
[set]
)
const setTradeType = useCallback((tradeType: 'Limit' | 'Market') => {
set((s) => {
s.tradeForm.tradeType = tradeType
})
}, [])
const handlePriceChange = useCallback(
(e: NumberFormatValues, info: SourceInfo) => {
@ -114,7 +112,7 @@ const AdvancedTradeForm = () => {
}
})
},
[set]
[]
)
const handleBaseSizeChange = useCallback(
@ -123,8 +121,8 @@ const AdvancedTradeForm = () => {
set((s) => {
const price =
s.tradeForm.tradeType === 'Market'
? marketPrice
: parseFloat(s.tradeForm.price)
? oraclePrice
: Number(s.tradeForm.price)
s.tradeForm.baseSize = e.value
if (price && e.value !== '' && !Number.isNaN(Number(e.value))) {
@ -134,7 +132,7 @@ const AdvancedTradeForm = () => {
}
})
},
[set, marketPrice]
[oraclePrice]
)
const handleQuoteSizeChange = useCallback(
@ -143,8 +141,8 @@ const AdvancedTradeForm = () => {
set((s) => {
const price =
s.tradeForm.tradeType === 'Market'
? marketPrice
: parseFloat(s.tradeForm.price)
? oraclePrice
: Number(s.tradeForm.price)
s.tradeForm.quoteSize = e.value
if (price && e.value !== '' && !Number.isNaN(Number(e.value))) {
@ -154,47 +152,55 @@ const AdvancedTradeForm = () => {
}
})
},
[set, marketPrice]
[oraclePrice]
)
const handlePostOnlyChange = useCallback(
(postOnly: boolean) => {
const handlePostOnlyChange = useCallback((postOnly: boolean) => {
set((s) => {
s.tradeForm.postOnly = postOnly
if (s.tradeForm.ioc === true) {
s.tradeForm.ioc = !postOnly
}
})
}, [])
const handleIocChange = useCallback((ioc: boolean) => {
set((s) => {
s.tradeForm.ioc = ioc
if (s.tradeForm.postOnly === true) {
s.tradeForm.postOnly = !ioc
}
})
}, [])
const handleSetSide = useCallback((side: 'buy' | 'sell') => {
set((s) => {
s.tradeForm.side = side
})
}, [])
/*
* Updates the limit price on page load
*/
useEffect(() => {
if (tradeForm.price === undefined) {
const group = mangoStore.getState().group
if (!group || !oraclePrice) return
set((s) => {
s.tradeForm.postOnly = postOnly
if (s.tradeForm.ioc === true) {
s.tradeForm.ioc = !postOnly
}
s.tradeForm.price = oraclePrice.toString()
})
},
[set]
)
const handleIocChange = useCallback(
(ioc: boolean) => {
set((s) => {
s.tradeForm.ioc = ioc
if (s.tradeForm.postOnly === true) {
s.tradeForm.postOnly = !ioc
}
})
},
[set]
)
const handleSetSide = useCallback(
(side: 'buy' | 'sell') => {
set((s) => {
s.tradeForm.side = side
})
},
[set]
)
}
}, [oraclePrice, tradeForm.price])
/*
* Updates the price and the quote size when a Market order is selected
*/
useEffect(() => {
const group = mangoStore.getState().group
if (
tradeForm.tradeType === 'Market' &&
marketPrice &&
oraclePrice &&
selectedMarket &&
group
) {
@ -209,20 +215,18 @@ const AdvancedTradeForm = () => {
}
if (!isNaN(parseFloat(tradeForm.baseSize))) {
const baseSize = new Decimal(tradeForm.baseSize)?.toNumber()
const orderbook = mangoStore.getState().selectedMarket.orderbook
const price = calculateMarketPrice(orderbook, baseSize, tradeForm.side)
const quoteSize = baseSize * price
const quoteSize = baseSize * oraclePrice
set((s) => {
s.tradeForm.price = price.toFixed(getDecimalCount(tickSize))
s.tradeForm.price = oraclePrice.toFixed(getDecimalCount(tickSize))
s.tradeForm.quoteSize = quoteSize.toFixed(getDecimalCount(tickSize))
})
} else {
set((s) => {
s.tradeForm.price = marketPrice.toFixed(getDecimalCount(tickSize))
s.tradeForm.price = oraclePrice.toFixed(getDecimalCount(tickSize))
})
}
}
}, [marketPrice, selectedMarket, tradeForm])
}, [oraclePrice, selectedMarket, tradeForm])
const handlePlaceOrder = useCallback(async () => {
const client = mangoStore.getState().client
@ -235,11 +239,15 @@ const AdvancedTradeForm = () => {
if (!group || !mangoAccount) return
setPlacingOrder(true)
try {
const baseSize = new Decimal(tradeForm.baseSize).toNumber()
let price = new Decimal(tradeForm.price).toNumber()
const baseSize = Number(tradeForm.baseSize)
let price = Number(tradeForm.price)
if (tradeForm.tradeType === 'Market') {
const orderbook = mangoStore.getState().selectedMarket.orderbook
price = calculateMarketPrice(orderbook, baseSize, tradeForm.side)
price = calculateLimitPriceForMarketOrder(
orderbook,
baseSize,
tradeForm.side
)
}
if (selectedMarket instanceof Serum3Market) {
@ -260,7 +268,6 @@ const AdvancedTradeForm = () => {
Date.now(),
10
)
actions.reloadMangoAccount()
actions.fetchOpenOrders()
notify({
type: 'success',
@ -289,7 +296,6 @@ const AdvancedTradeForm = () => {
undefined,
undefined
)
actions.reloadMangoAccount()
actions.fetchOpenOrders()
notify({
type: 'success',
@ -308,7 +314,7 @@ const AdvancedTradeForm = () => {
} finally {
setPlacingOrder(false)
}
}, [t])
}, [])
const maintProjectedHealth = useMemo(() => {
const group = mangoStore.getState().group
@ -344,13 +350,13 @@ const AdvancedTradeForm = () => {
group,
selectedMarket.perpMarketIndex,
parseFloat(tradeForm.baseSize),
parseFloat(tradeForm.price)
Number(tradeForm.price)
)
: mangoAccount.simHealthRatioWithPerpBidUiChanges(
group,
selectedMarket.perpMarketIndex,
parseFloat(tradeForm.baseSize),
parseFloat(tradeForm.price)
Number(tradeForm.price)
)
}
} catch (e) {

View File

@ -23,13 +23,13 @@ const PerpButtonGroup = () => {
return mangoAccount.getMaxQuoteForPerpBidUi(
group,
selectedMarket.perpMarketIndex,
parseFloat(tradeFormPrice)
Number(tradeFormPrice)
)
} else {
return mangoAccount.getMaxBaseForPerpAskUi(
group,
selectedMarket.perpMarketIndex,
parseFloat(tradeFormPrice)
Number(tradeFormPrice)
)
}
} catch (e) {
@ -53,9 +53,7 @@ const PerpButtonGroup = () => {
s.tradeForm.quoteSize = size.toString()
if (Number(s.tradeForm.price)) {
s.tradeForm.baseSize = (
size / parseFloat(s.tradeForm.price)
).toString()
s.tradeForm.baseSize = (size / Number(s.tradeForm.price)).toString()
} else {
s.tradeForm.baseSize = ''
}
@ -64,7 +62,7 @@ const PerpButtonGroup = () => {
if (Number(s.tradeForm.price)) {
s.tradeForm.quoteSize = (
size * parseFloat(s.tradeForm.price)
size * Number(s.tradeForm.price)
).toString()
}
}

View File

@ -4,7 +4,6 @@ import { Table, Td, Th, TrBody, TrHead } from '@components/shared/TableElements'
import { LinkIcon, NoSymbolIcon } from '@heroicons/react/20/solid'
import { useWallet } from '@solana/wallet-adapter-react'
import mangoStore from '@store/mangoStore'
import Decimal from 'decimal.js'
import useMangoGroup from 'hooks/useMangoGroup'
import useSelectedMarket from 'hooks/useSelectedMarket'
import { useTranslation } from 'next-i18next'
@ -14,7 +13,7 @@ import {
numberFormat,
trimDecimals,
} from 'utils/numbers'
import { calculateMarketPrice } from 'utils/tradeForm'
import { calculateLimitPriceForMarketOrder } from 'utils/tradeForm'
import PerpSideBadge from './PerpSideBadge'
import TableMarketName from './TableMarketName'
@ -29,10 +28,14 @@ const PerpPositions = () => {
const tradeForm = mangoStore.getState().tradeForm
const set = mangoStore.getState().set
let price = new Decimal(tradeForm.price).toNumber()
let price = Number(tradeForm.price)
if (tradeForm.tradeType === 'Market') {
const orderbook = mangoStore.getState().selectedMarket.orderbook
price = calculateMarketPrice(orderbook, positionSize, tradeForm.side)
price = calculateLimitPriceForMarketOrder(
orderbook,
positionSize,
tradeForm.side
)
}
const newSide = positionSize > 0 ? 'sell' : 'buy'

View File

@ -29,13 +29,13 @@ const PerpSlider = () => {
return mangoAccount.getMaxQuoteForPerpBidUi(
group,
selectedMarket.perpMarketIndex,
parseFloat(tradeForm.price)
Number(tradeForm.price)
)
} else {
return mangoAccount.getMaxBaseForPerpAskUi(
group,
selectedMarket.perpMarketIndex,
parseFloat(tradeForm.price)
Number(tradeForm.price)
)
}
} catch (e) {
@ -56,7 +56,7 @@ const PerpSlider = () => {
const price =
s.tradeForm.tradeType === 'Market'
? marketPrice
: parseFloat(s.tradeForm.price)
: Number(s.tradeForm.price)
if (s.tradeForm.side === 'buy') {
s.tradeForm.quoteSize = val

View File

@ -50,9 +50,7 @@ const SpotButtonGroup = () => {
s.tradeForm.quoteSize = size.toString()
if (Number(s.tradeForm.price)) {
s.tradeForm.baseSize = (
size / parseFloat(s.tradeForm.price)
).toString()
s.tradeForm.baseSize = (size / Number(s.tradeForm.price)).toString()
} else {
s.tradeForm.baseSize = ''
}
@ -61,7 +59,7 @@ const SpotButtonGroup = () => {
if (Number(s.tradeForm.price)) {
s.tradeForm.quoteSize = (
size * parseFloat(s.tradeForm.price)
size * Number(s.tradeForm.price)
).toString()
}
}

View File

@ -47,7 +47,7 @@ const SpotSlider = () => {
const price =
s.tradeForm.tradeType === 'Market'
? marketPrice
: parseFloat(s.tradeForm.price)
: Number(s.tradeForm.price)
if (s.tradeForm.side === 'buy') {
s.tradeForm.quoteSize = val

View File

@ -45,7 +45,6 @@ const UnsettledTrades = ({
new PublicKey(mktAddress)
)
actions.fetchOpenOrders()
actions.reloadMangoAccount()
notify({
type: 'success',
title: 'Successfully settled funds',

View File

@ -4,7 +4,10 @@ import type { NextPage } from 'next'
import { ReactNode } from 'react'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import useMangoAccount from 'hooks/useMangoAccount'
import { toUiDecimalsForQuote, HealthType } from '@blockworks-foundation/mango-v4';
import {
toUiDecimalsForQuote,
HealthType,
} from '@blockworks-foundation/mango-v4'
export async function getStaticProps({ locale }: { locale: string }) {
return {
@ -56,19 +59,27 @@ const Dashboard: NextPage = () => {
/>
<KeyValuePair
label="Init Health"
value={`$${toUiDecimalsForQuote(mangoAccount.getHealth(group, HealthType.init)).toFixed(4)}`}
value={`$${toUiDecimalsForQuote(
mangoAccount.getHealth(group, HealthType.init)
).toFixed(4)}`}
/>
<KeyValuePair
label="Maint Health"
value={`$${toUiDecimalsForQuote(mangoAccount.getHealth(group, HealthType.maint)).toFixed(4)}`}
value={`$${toUiDecimalsForQuote(
mangoAccount.getHealth(group, HealthType.maint)
).toFixed(4)}`}
/>
<KeyValuePair
label="Perp Settle Health"
value={`$${toUiDecimalsForQuote(mangoAccount.getPerpSettleHealth(group)).toFixed(4)}`}
value={`$${toUiDecimalsForQuote(
mangoAccount.getPerpSettleHealth(group)
).toFixed(4)}`}
/>
<KeyValuePair
label="Net Deposits"
value={`$${toUiDecimalsForQuote(mangoAccount.netDeposits).toFixed(4)}`}
value={`$${toUiDecimalsForQuote(
mangoAccount.netDeposits
).toFixed(4)}`}
/>
<KeyValuePair
label="Perp Spot Transfers"
@ -154,7 +165,9 @@ const Dashboard: NextPage = () => {
/>
<KeyValuePair
label="Quote Position UI"
value={`$${toUiDecimalsForQuote(perp.quotePositionNative).toFixed(4)}`}
value={`$${toUiDecimalsForQuote(
perp.quotePositionNative
).toFixed(4)}`}
/>
<KeyValuePair
label="Quote Running Native"

View File

@ -1,5 +1,10 @@
import {
Group,
PerpMarket,
Serum3Market,
} from '@blockworks-foundation/mango-v4'
import TradeAdvancedPage from '@components/trade/TradeAdvancedPage'
import mangoStore from '@store/mangoStore'
import mangoStore, { DEFAULT_TRADE_FORM } from '@store/mangoStore'
// import mangoStore from '@store/mangoStore'
import type { NextPage } from 'next'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
@ -20,6 +25,23 @@ export async function getStaticProps({ locale }: { locale: string }) {
}
}
const getOraclePriceForMarket = (
group: Group,
mkt: Serum3Market | PerpMarket
): number => {
let price: number
if (mkt instanceof Serum3Market) {
const baseBank = group.getFirstBankByTokenIndex(mkt.baseTokenIndex)
price = baseBank.uiPrice
} else if (mkt) {
price = mkt._uiPrice
} else {
price = 0
}
return price
}
const Trade: NextPage = () => {
const router = useRouter()
const { name: marketName } = router.query
@ -34,10 +56,15 @@ const Trade: NextPage = () => {
const mkt =
serumMarkets.find((m) => m.name === marketName) ||
perpMarkets.find((m) => m.name === marketName)
if (mkt) {
set((s) => {
s.selectedMarket.name = marketName
s.selectedMarket.current = mkt
s.tradeForm = {
...DEFAULT_TRADE_FORM,
price: getOraclePriceForMarket(group, mkt).toString(),
}
})
}
}

View File

@ -5,7 +5,7 @@ import { subscribeWithSelector } from 'zustand/middleware'
import { AnchorProvider, Wallet, web3 } from '@project-serum/anchor'
import { Connection, Keypair, PublicKey } from '@solana/web3.js'
import { OpenOrders, Order } from '@project-serum/serum/lib/market'
import { Orderbook as SpotOrderBook } from '@project-serum/serum'
import { Orderbook } from '@project-serum/serum'
import { Wallet as WalletAdapter } from '@solana/wallet-adapter-react'
import {
MangoClient,
@ -33,7 +33,7 @@ import {
LAST_ACCOUNT_KEY,
OUTPUT_TOKEN_DEFAULT,
} from '../utils/constants'
import { Orderbook, SpotBalances } from 'types'
import { OrderbookL2, SpotBalances } from 'types'
import spotBalancesUpdater from './spotBalancesUpdater'
import { PerpMarket } from '@blockworks-foundation/mango-v4/'
import perpPositionsUpdater from './perpPositionsUpdater'
@ -175,6 +175,26 @@ export interface TokenStatsItem {
// wallet_pk: '',
// }
interface TradeForm {
side: 'buy' | 'sell'
price: string | undefined
baseSize: string
quoteSize: string
tradeType: 'Market' | 'Limit'
postOnly: boolean
ioc: boolean
}
export const DEFAULT_TRADE_FORM: TradeForm = {
side: 'buy',
price: undefined,
baseSize: '',
quoteSize: '',
tradeType: 'Limit',
postOnly: false,
ioc: false,
}
export type MangoStore = {
activityFeed: {
feed: Array<DepositWithdrawFeedItem | LiquidationFeedItem>
@ -213,9 +233,9 @@ export type MangoStore = {
name: string
current: Serum3Market | PerpMarket | undefined
fills: any
bidsAccount: BookSide | SpotOrderBook | undefined
asksAccount: BookSide | SpotOrderBook | undefined
orderbook: Orderbook
bidsAccount: BookSide | Orderbook | undefined
asksAccount: BookSide | Orderbook | undefined
orderbook: OrderbookL2
markPrice: number
}
serumMarkets: Serum3Market[]
@ -243,15 +263,7 @@ export type MangoStore = {
loading: boolean
data: TokenStatsItem[]
}
tradeForm: {
side: 'buy' | 'sell'
price: string
baseSize: string
quoteSize: string
tradeType: 'Market' | 'Limit'
postOnly: boolean
ioc: boolean
}
tradeForm: TradeForm
wallet: {
tokens: TokenAccount[]
nfts: {
@ -364,15 +376,7 @@ const mangoStore = create<MangoStore>()(
loading: false,
data: [],
},
tradeForm: {
side: 'buy',
price: '',
baseSize: '',
quoteSize: '',
tradeType: 'Limit',
postOnly: false,
ioc: false,
},
tradeForm: DEFAULT_TRADE_FORM,
wallet: {
tokens: [],
nfts: {
@ -673,6 +677,9 @@ const mangoStore = create<MangoStore>()(
const set = get().set
const client = get().client
const group = get().group
if (!providedMangoAccount) {
await get().actions.reloadMangoAccount()
}
const mangoAccount =
providedMangoAccount || get().mangoAccount.current

View File

@ -11,7 +11,7 @@ export interface ChartTradeType {
marketAddress: string
}
export interface Orderbook {
export interface OrderbookL2 {
bids: number[][]
asks: number[][]
}

View File

@ -1,7 +1,7 @@
import { Orderbook } from 'types'
import { OrderbookL2 } from 'types'
export const calculateMarketPrice = (
orderBook: Orderbook,
export const calculateLimitPriceForMarketOrder = (
orderBook: OrderbookL2,
size: number,
side: 'buy' | 'sell'
): number => {
@ -29,7 +29,7 @@ export const calculateMarketPrice = (
}
export const calculateSlippage = (
orderBook: Orderbook,
orderBook: OrderbookL2,
size: number,
side: 'buy' | 'sell',
markPrice: number
@ -39,7 +39,11 @@ export const calculateSlippage = (
const referencePrice = bb && ba ? (bb + ba) / 2 : markPrice
if (Number(size)) {
const estimatedPrice = calculateMarketPrice(orderBook, Number(size), side)
const estimatedPrice = calculateLimitPriceForMarketOrder(
orderBook,
Number(size),
side
)
const slippageAbs =
Number(size) > 0 ? Math.abs(estimatedPrice - referencePrice) : 0