feat: add market summary to orderbook (#1103)

Add market summary statistics to orderbook

Co-authored-by: Saurabh Singh <saurabhsraghuvanshi@gmail.com>
This commit is contained in:
kunal yadav 2026-02-18 18:19:03 +05:30 committed by theborakompanioni
parent 2d2cfa21b5
commit 59262a84db
No known key found for this signature in database
GPG key ID: E8070AF0053AAC0D
2 changed files with 81 additions and 0 deletions

View file

@ -9,6 +9,7 @@ import { DevBadge } from '@/components/dev/DevBadge'
import { Alert, AlertDescription } from '@/components/ui/alert'
import { Button } from '@/components/ui/button'
import { ButtonGroup } from '@/components/ui/button-group'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import {
DropdownMenu,
DropdownMenuContent,
@ -189,6 +190,43 @@ export const OrderbookContent = ({ enabled, className }: OrderbookContentProps)
}
}, [tableRowModel])
const marketSummary = useMemo(() => {
const entries = (tableRowModel?.rows ?? []).map((r) => r.original)
if (entries.length === 0) return null
const absoluteOffers = entries.filter((offer) => offer.type.isAbsolute)
const relativeOffers = entries.filter((offer) => offer.type.isRelative)
const counterpartyBonds = new Map<string, number>()
for (const offer of entries) {
const previous = counterpartyBonds.get(offer.counterparty) ?? 0
if (offer.bondValue.value > previous) counterpartyBonds.set(offer.counterparty, offer.bondValue.value)
}
const bondedMakers = [...counterpartyBonds.values()].filter((v) => v > 0).length
const median = (values: number[]) => {
if (values.length === 0) return 0
// eslint-disable-next-line unicorn/no-array-sort -- toSorted() not supported in target browsers
const sorted = [...values].sort((a, b) => a - b)
const mid = Math.floor(sorted.length / 2)
return sorted.length % 2 !== 0 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2
}
const maxSizes = entries.map((offer) => Number(offer.maximumSize))
const minSizes = entries.map((offer) => Number(offer.minimumSize))
return {
medianAbsFee:
absoluteOffers.length > 0 ? Math.round(median(absoluteOffers.map((offer) => offer.fee.value))) : null,
medianRelFee: relativeOffers.length > 0 ? median(relativeOffers.map((offer) => offer.fee.value)) : null,
totalLiquidity: maxSizes.reduce((sum, v) => sum + v, 0),
minOfferSize: Math.min(...minSizes),
maxOfferSize: Math.max(...maxSizes),
bondedMakers,
unbondedMakers: counterpartyBonds.size - bondedMakers,
}
}, [tableRowModel])
const handleClearAndReload = async () => {
await refetchOrderbookRefresh().then(() => refetchOrderbookData())
}
@ -325,6 +363,43 @@ export const OrderbookContent = ({ enabled, className }: OrderbookContentProps)
</div>
)}
{marketSummary && (
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium">{t('orderbook.market_summary_title')}</CardTitle>
</CardHeader>
<CardContent className="text-muted-foreground grid gap-1 text-sm sm:grid-cols-2">
{marketSummary.medianAbsFee !== null && (
<p>{t('orderbook.market_summary_median_abs_fee', { value: marketSummary.medianAbsFee.toLocaleString() })}</p>
)}
{marketSummary.medianRelFee !== null && (
<p>
{t('orderbook.market_summary_median_rel_fee', {
value: factorToPercentage(marketSummary.medianRelFee).toFixed(4),
})}
</p>
)}
<p>
{t('orderbook.market_summary_total_liquidity', {
value: marketSummary.totalLiquidity.toLocaleString(),
})}
</p>
<p>
{t('orderbook.market_summary_offer_size_range', {
min: marketSummary.minOfferSize.toLocaleString(),
max: marketSummary.maxOfferSize.toLocaleString(),
})}
</p>
<p>
{t('orderbook.market_summary_bonded_makers', {
bonded: marketSummary.bondedMakers,
unbonded: marketSummary.unbondedMakers,
})}
</p>
</CardContent>
</Card>
)}
{isLoadingInitially ? (
<div className="py-12">
<div className="text-muted-foreground m-2 flex items-center justify-center gap-2">

View file

@ -729,6 +729,12 @@
"text_highlight_own_orders_subtitle": "It might take some time for your offer and your bond to appear in your local orderbook.",
"label_pin_to_top_own_orders": "Pin my offers",
"text_pin_to_top_own_orders_subtitle": "Displays your offers at the top of the table",
"market_summary_title": "Market Summary",
"market_summary_median_abs_fee": "Median absolute fee: {{ value }} sats",
"market_summary_median_rel_fee": "Median relative fee: {{ value }}%",
"market_summary_total_liquidity": "Total liquidity: {{ value }} sats",
"market_summary_offer_size_range": "Offer size range: {{ min }} {{ max }} sats",
"market_summary_bonded_makers": "{{ bonded }} bonded / {{ unbonded }} unbonded makers",
"table": {
"heading_type": "Type",
"heading_counterparty": "Counterparty",