Skip to content

Commit

Permalink
feat: tidy up quote, fix quote errors
Browse files Browse the repository at this point in the history
  • Loading branch information
earthtojake authored Nov 13, 2022
1 parent 73951fd commit fe1978f
Show file tree
Hide file tree
Showing 34 changed files with 580 additions and 287 deletions.
40 changes: 18 additions & 22 deletions app/components/common/OptionStatsGrid/index.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,49 @@
import Box from '@lyra/ui/components/Box'
import Grid from '@lyra/ui/components/Grid'
import useIsMobile from '@lyra/ui/hooks/useIsMobile'
import formatNumber from '@lyra/ui/utils/formatNumber'
import formatPercentage from '@lyra/ui/utils/formatPercentage'
import formatTruncatedUSD from '@lyra/ui/utils/formatTruncatedUSD'
import formatUSD from '@lyra/ui/utils/formatUSD'
import { Option } from '@lyrafinance/lyra-js'
import { Option, Quote } from '@lyrafinance/lyra-js'
import React from 'react'

import { UNIT } from '@/app/constants/bn'
import { MAX_IV } from '@/app/constants/contracts'
import fromBigNumber from '@/app/utils/fromBigNumber'
import getDefaultQuoteSize from '@/app/utils/getDefaultQuoteSize'

import LabelItem from '../LabelItem'

type Props = {
option: Option
isBuy: boolean
bid: Quote | null
ask: Quote | null
}

const OptionStatsGrid = ({ option, isBuy }: Props) => {
const size = getDefaultQuoteSize(option.market().name)
const bid = option.quoteSync(true, size)
const ask = option.quoteSync(false, size)
const quote = isBuy ? bid : ask
const OptionStatsGrid = ({ option, bid, ask }: Props) => {
const openInterest = option.longOpenInterest.add(option.shortOpenInterest).mul(option.market().spotPrice).div(UNIT)
const isMobile = useIsMobile()
return (
<Grid sx={{ gridTemplateColumns: ['1fr 1fr', '1fr 1fr 1fr 1fr 1fr'], gridColumnGap: [3, 6], gridRowGap: 8 }}>
<Grid sx={{ gridTemplateColumns: ['1fr 1fr', '1fr 1fr 1fr 1fr 1fr'], gridColumnGap: [3, 6], gridRowGap: [3, 6] }}>
<LabelItem
label="Bid"
value={!ask.isDisabled && ask.pricePerOption.gt(0) ? formatUSD(ask.pricePerOption) : '-'}
value={bid && !bid.isDisabled && bid.pricePerOption.gt(0) ? formatUSD(bid.pricePerOption) : '-'}
/>
<LabelItem
label="Ask"
value={!bid.isDisabled && bid.pricePerOption.gt(0) ? formatUSD(bid.pricePerOption) : '-'}
value={ask && !ask.isDisabled && ask.pricePerOption.gt(0) ? formatUSD(ask.pricePerOption) : '-'}
/>
<LabelItem
label="Implied Volatility"
value={quote.iv.gt(0) && quote.iv.lt(MAX_IV) ? formatPercentage(fromBigNumber(quote.iv), true) : '-'}
label="Bid IV"
value={bid && bid.iv.gt(0) && bid.iv.lt(MAX_IV) ? formatPercentage(fromBigNumber(bid.iv), true) : '-'}
/>
<LabelItem
label="Ask IV"
value={ask && ask.iv.gt(0) && ask.iv.lt(MAX_IV) ? formatPercentage(fromBigNumber(ask.iv), true) : '-'}
/>
<LabelItem label="Open Interest" value={openInterest.gt(0) ? formatTruncatedUSD(openInterest) : '-'} />
{!isMobile ? <Box /> : null}
<LabelItem label="Delta" value={formatNumber(quote.greeks.delta, { dps: 3 })} />
<LabelItem label="Vega" value={formatNumber(quote.greeks.vega, { dps: 3 })} />
<LabelItem label="Gamma" value={formatNumber(quote.greeks.gamma, { dps: 3 })} />
<LabelItem label="Theta" value={formatNumber(quote.greeks.theta, { dps: 3 })} />
<LabelItem label="Rho" value={formatNumber(quote.greeks.rho, { dps: 3 })} />
<LabelItem label="Delta" value={formatNumber(option.delta, { dps: 3 })} />
<LabelItem label="Vega" value={formatNumber(option.strike().vega, { dps: 3 })} />
<LabelItem label="Gamma" value={formatNumber(option.strike().gamma, { dps: 3 })} />
<LabelItem label="Theta" value={formatNumber(option.theta, { dps: 3 })} />
<LabelItem label="Rho" value={formatNumber(option.rho, { dps: 3 })} />
</Grid>
)
}
Expand Down
11 changes: 7 additions & 4 deletions app/components/position/PositionStatsCard/index.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
import Card from '@lyra/ui/components/Card'
import CardSection from '@lyra/ui/components/Card/CardSection'
import Text from '@lyra/ui/components/Text'
import { Option, Position } from '@lyrafinance/lyra-js'
import { Option } from '@lyrafinance/lyra-js'
import React from 'react'

import useOptionQuotesSync from '@/app/hooks/market/useOptionQuotesSync'
import getDefaultQuoteSize from '@/app/utils/getDefaultQuoteSize'

import OptionStatsGrid from '../../common/OptionStatsGrid'

type Props = {
position: Position
option: Option
}

const PositionStatsCard = ({ option, position }: Props): JSX.Element | null => {
const PositionStatsCard = ({ option }: Props): JSX.Element | null => {
const { bid, ask } = useOptionQuotesSync(option, getDefaultQuoteSize(option.market().name))
return (
<Card>
<CardSection>
<Text variant="heading" mb={6}>
Stats
</Text>
<OptionStatsGrid option={option} isBuy={position.isLong} />
<OptionStatsGrid option={option} bid={bid} ask={ask} />
</CardSection>
</Card>
)
Expand Down
70 changes: 70 additions & 0 deletions app/components/trade/TradeBoardNoticeSection/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import Alert from '@lyra/ui/components/Alert'
import { IconType } from '@lyra/ui/components/Icon'
import { MarginProps } from '@lyra/ui/types'
import formatTruncatedDuration from '@lyra/ui/utils/formatTruncatedDuration'
import { Board } from '@lyrafinance/lyra-js'
import React from 'react'

import { MAX_UTILIZATION, OptionQuotesNullable, StrikeQuotesNullable } from '@/app/constants/contracts'
import { TRADING_CUTOFF_DOC_URL } from '@/app/constants/links'

type Props = {
board: Board
isGlobalPaused: boolean
quotes: (StrikeQuotesNullable | OptionQuotesNullable)[]
} & MarginProps

export default function TradeBoardNoticeSection({ board, isGlobalPaused, quotes, ...marginProps }: Props) {
const market = board.market()
if (board.isTradingCutoff || board.isExpired) {
return (
<Alert
icon={IconType.AlertTriangle}
title="Trading Cutoff"
variant="default"
description={`Trading ends ${formatTruncatedDuration(
board.expiryTimestamp - board.tradingCutoffTimestamp
)} before
expiration. Traders can still close their positions for this expiry.`}
linkHref={TRADING_CUTOFF_DOC_URL}
{...marginProps}
/>
)
} else if (isGlobalPaused || board.isPaused) {
return (
<Alert
icon={IconType.AlertTriangle}
title="Trading Paused"
variant="warning"
description={isGlobalPaused ? 'Trading is paused.' : `Trading for the ${market.name} market is paused.`}
{...marginProps}
/>
)
} else if (market.liquidity.utilization >= MAX_UTILIZATION) {
return (
<Alert
icon={IconType.AlertTriangle}
title="High Utilization"
variant="warning"
description={`The ${market.name} market is ${
market.liquidity.utilization === 1 ? 'fully utilized' : 'almost fully utilized'
}. When a vault is fully utilized new positions can be opened. Traders can still close their ${
market.name
} positions.`}
{...marginProps}
/>
)
} else if (!quotes.length) {
return (
<Alert
icon={IconType.Info}
title="No Available Strikes"
description="This expiry has no available strikes in delta range. Traders can still close their positions for this expiry."
linkHref={TRADING_CUTOFF_DOC_URL}
{...marginProps}
/>
)
} else {
return null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,25 @@ import { TradeBoardTableOrListProps } from '.'
import TradeBoardPriceButton from './TradeBoardPriceButton'

const TradeBoardListMobile = ({
isCall,
isBuy,
quotes,
selectedOption,
onSelectOption,
...styleProps
}: TradeBoardTableOrListProps): ListElement => {
const [expandedQuote, setExpandedQuote] = useState<{ bid: Quote; ask: Quote; option: Option } | null>(null)
const [expandedQuote, setExpandedQuote] = useState<{ bid: Quote | null; ask: Quote | null; option: Option } | null>(
null
)
const strikeId = selectedOption?.strike().id
return (
<Box {...styleProps}>
<Box>
{quotes.map(({ option, bid, ask }) => {
const quote = isBuy ? bid : ask
const quote = isBuy ? ask : bid
const strike = option.strike()
if (!quote) {
return null
}
return (
<React.Fragment key={option.strike().id}>
<React.Fragment key={strike.id}>
<CardSection
onClick={() => {
setExpandedQuote({ option, bid, ask })
Expand Down Expand Up @@ -78,7 +82,7 @@ const TradeBoardListMobile = ({
expandedQuote ? (
<Text variant="heading" ml={6} mt={6}>
{expandedQuote.option.market().name} ${fromBigNumber(expandedQuote.option.strike().strikePrice)}{' '}
{isCall ? 'Call' : 'Put'}
{expandedQuote.option.isCall ? 'Call' : 'Put'}
<Text as="span" color="secondaryText">
&nbsp;·&nbsp;Exp. {formatDate(expandedQuote.option.board().expiryTimestamp, true)}
</Text>
Expand All @@ -90,7 +94,7 @@ const TradeBoardListMobile = ({
>
{expandedQuote ? (
<CardSection>
<OptionStatsGrid option={expandedQuote.option} isBuy={isBuy} />
<OptionStatsGrid {...expandedQuote} />
</CardSection>
) : null}
</Modal>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Quote, QuoteDisabledReason } from '@lyrafinance/lyra-js'
import React from 'react'

import emptyFunction from '@/app/utils/emptyFunction'
import getIsQuoteHidden from '@/app/utils/getIsQuoteHidden'

type Props = {
quote: Quote
Expand Down Expand Up @@ -33,15 +34,6 @@ const getDisabledMessage = (disabledReason: QuoteDisabledReason): string => {
}
}

const getIsButtonDisabled = (disabledReason: QuoteDisabledReason): boolean => {
switch (disabledReason) {
case QuoteDisabledReason.InsufficientLiquidity:
return false
default:
return true
}
}

const getButtonWidthForMarket = (marketName: string) => {
switch (marketName.toLowerCase()) {
case 'btc':
Expand All @@ -57,8 +49,8 @@ export default function TradeBoardPriceButton({
onSelected = emptyFunction,
...styleProps
}: Props): ButtonElement {
const { isCall, isBuy, disabledReason } = quote
const isDisabled = disabledReason ? getIsButtonDisabled(disabledReason) : false
const { isBuy, disabledReason } = quote
const isDisabled = disabledReason ? getIsQuoteHidden(disabledReason) : false
return (
<Button
variant={isBuy ? 'primary' : 'error'}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
import Box from '@lyra/ui/components/Box'
import CardSection from '@lyra/ui/components/Card/CardSection'
import Table, { TableCellProps, TableColumn, TableData, TableElement } from '@lyra/ui/components/Table'
import Table, { TableCellProps, TableColumn, TableData } from '@lyra/ui/components/Table'
import Text from '@lyra/ui/components/Text'
import filterNulls from '@lyra/ui/utils/filterNulls'
import formatPercentage from '@lyra/ui/utils/formatPercentage'
import formatUSD from '@lyra/ui/utils/formatUSD'
import { Quote, QuoteDisabledReason } from '@lyrafinance/lyra-js'
import React, { useMemo, useState } from 'react'

import { ONE_BN, ZERO_BN } from '@/app/constants/bn'
import { MAX_IV } from '@/app/constants/contracts'
import { LogEvent } from '@/app/constants/logEvents'
import fromBigNumber from '@/app/utils/fromBigNumber'
import getDefaultQuoteSize from '@/app/utils/getDefaultQuoteSize'
import logEvent, { LogData } from '@/app/utils/logEvent'

import OptionStatsGrid from '../../common/OptionStatsGrid'
Expand Down Expand Up @@ -42,31 +39,34 @@ const getLogData = (quote: Quote): LogData => ({
})

const TradeBoardTableDesktop = ({
board,
quotes,
isBuy,
selectedOption,
onSelectOption,
quotes,
}: TradeBoardTableOrListProps): TableElement<OptionData> => {
}: TradeBoardTableOrListProps) => {
const [expandedStrikes, setExpandedStrikes] = useState<Record<string, boolean>>({})
const market = quotes.length ? quotes[0].option.market() : null
const size = getDefaultQuoteSize(market?.name ?? '') // defaults to one
const spotPrice = fromBigNumber(market?.spotPrice ?? ZERO_BN)
const spotPrice = fromBigNumber(board.market().spotPrice)
const rows: OptionData[] = useMemo(() => {
const rows = filterNulls(
quotes.map(({ bid, ask, option }) => {
const quote = isBuy ? bid : ask
const quote = isBuy ? ask : bid
if (!quote) {
return null
}
const isExpanded = !!expandedStrikes[quote.strike().id.toString()]
const id = quote.strike().id.toString()
const strike = option.strike()
const strikeId = strike.id
return {
id,
id: strikeId.toString(),
quote,
strikeId: quote.strike().id,
strikeId,
marketAddressOrName: quote.market().address,
strike: fromBigNumber(quote.strike().strikePrice),
strike: fromBigNumber(strike.strikePrice),
iv: fromBigNumber(quote.iv),
price: fromBigNumber(quote.pricePerOption),
breakEven: fromBigNumber(quote.breakEven),
toBreakEven: fromBigNumber(quote.breakEven.sub(market?.spotPrice ?? ZERO_BN)),
toBreakEven: fromBigNumber(quote.toBreakEven),
disabledReason: quote.disabledReason,
isExpanded,
onToggleExpand: (isExpanded: boolean) => {
Expand All @@ -79,18 +79,18 @@ const TradeBoardTableDesktop = ({
isExpandedContentClickable: true,
expanded: (
<Box pb={4} px={3}>
<Text variant="bodyMedium" mb={4}>
<Text variant="bodyMedium" mb={6}>
Stats
</Text>
<OptionStatsGrid option={option} isBuy={isBuy} />
<OptionStatsGrid option={option} bid={bid} ask={ask} />
</Box>
),
}
})
)

return rows
}, [quotes, isBuy, expandedStrikes, market?.spotPrice])
}, [quotes, isBuy, expandedStrikes])

const columns = useMemo<TableColumn<OptionData>[]>(
() => [
Expand Down Expand Up @@ -132,7 +132,7 @@ const TradeBoardTableDesktop = ({
},
{
accessor: 'price',
Header: !market || size.eq(ONE_BN) ? 'Price' : `Price (${fromBigNumber(size)} ${market.name})`,
Header: 'Price',
disableSortBy: true,
Cell: (props: TableCellProps<OptionData>) => {
const { quote, strikeId } = props.row.original
Expand All @@ -147,7 +147,7 @@ const TradeBoardTableDesktop = ({
},
},
],
[market, size, selectedOption, onSelectOption]
[selectedOption, onSelectOption]
)

const spotPriceMarker = useMemo(() => {
Expand All @@ -158,11 +158,11 @@ const TradeBoardTableDesktop = ({
return { rowIdx: spotPriceRowIdx, content: formatUSD(spotPrice) }
}, [spotPrice, rows])

return (
<CardSection noPadding>
<Table data={rows} columns={columns} tableRowMarker={spotPriceMarker} />
</CardSection>
)
if (rows.length === 0) {
return null
}

return <Table data={rows} columns={columns} tableRowMarker={spotPriceMarker} />
}

export default TradeBoardTableDesktop
Loading

0 comments on commit fe1978f

Please sign in to comment.