diff --git a/condor/web/routes/market.py b/condor/web/routes/market.py
index b5b92a99..9afc2388 100644
--- a/condor/web/routes/market.py
+++ b/condor/web/routes/market.py
@@ -153,6 +153,35 @@ async def get_trading_rules(
return TradingRulesResponse(connector=connector, rules=rules)
+@router.get("/servers/{name}/market/pair-volumes")
+async def get_pair_volumes(
+ name: str,
+ connector: str = Query(...),
+ user: WebUser = Depends(get_current_user),
+):
+ """24h quote volume per trading pair, used to rank/curate the pair selector.
+
+ Returns {"connector": str, "volumes": {trading_pair: quote_volume_24h}}. A value of 0.0
+ marks a listed-but-untraded market. Only supported on some exchanges (hyperliquid, binance,
+ okx and their perp variants); returns an empty map for the rest so the caller falls back to
+ an alphabetical list.
+ """
+ cm = get_config_manager()
+ if not cm.has_server_access(user.id, name):
+ raise HTTPException(status_code=403, detail="No access")
+
+ client = await cm.get_client(name)
+ try:
+ result = await client.market_data.get_24h_volumes(connector)
+ except Exception as e:
+ # Unsupported connector (400) or upstream error — degrade to alphabetical ordering.
+ logger.info(f"pair-volumes unavailable for {connector}: {e}")
+ return {"connector": connector, "volumes": {}}
+
+ volumes = result.get("volumes", {}) if isinstance(result, dict) else {}
+ return {"connector": connector, "volumes": volumes}
+
+
@router.get("/servers/{name}/market/order-book", response_model=OrderBookResponse)
async def get_order_book(
name: str,
diff --git a/frontend/src/components/market/PairSelector.tsx b/frontend/src/components/market/PairSelector.tsx
index 3627194c..91fe3791 100644
--- a/frontend/src/components/market/PairSelector.tsx
+++ b/frontend/src/components/market/PairSelector.tsx
@@ -13,6 +13,25 @@ interface PairSelectorProps {
const MAX_VISIBLE = 50;
+// Quote-asset display priority. Ranking by raw 24h volume alone mixes currencies (a fiat-quoted
+// pair like USDT-IDR has a huge numeric quote volume), so we group by quote first, then sort by
+// volume within each group. Single-quote exchanges (e.g. Hyperliquid, all USDC) become pure
+// volume ranking.
+const QUOTE_PRIORITY = ["USDT", "USDC", "USD", "BTC", "ETH"];
+
+function quoteRank(pair: string): number {
+ const q = pair.split("-").pop() ?? "";
+ const i = QUOTE_PRIORITY.indexOf(q);
+ return i === -1 ? QUOTE_PRIORITY.length : i;
+}
+
+function formatVolume(v: number): string {
+ if (v >= 1e9) return `${(v / 1e9).toFixed(1)}B`;
+ if (v >= 1e6) return `${(v / 1e6).toFixed(1)}M`;
+ if (v >= 1e3) return `${(v / 1e3).toFixed(0)}K`;
+ return `${Math.round(v)}`;
+}
+
export function PairSelector({
server,
connector,
@@ -33,10 +52,37 @@ export function PairSelector({
staleTime: 5 * 60 * 1000,
});
- const pairs = useMemo(
- () => rulesData?.rules?.map((r) => r.trading_pair).sort() ?? [],
- [rulesData],
- );
+ // 24h quote volume per pair, for ranking + hiding untraded markets. Empty/unsupported → falls
+ // back to an alphabetical list.
+ const { data: volData } = useQuery({
+ queryKey: ["pair-volumes", server, connector],
+ queryFn: () => api.getPairVolumes(server, connector),
+ enabled: !!server && !!connector,
+ staleTime: 60 * 1000,
+ });
+
+ const pairs = useMemo(() => {
+ const all = rulesData?.rules?.map((r) => r.trading_pair) ?? [];
+ const volumes = volData?.volumes ?? {};
+ const hasVolume = Object.keys(volumes).length > 0;
+ if (!hasVolume) return [...all].sort();
+
+ return all
+ // Hide listed-but-untraded markets (24h volume exactly 0, e.g. Hyperliquid's tokenized
+ // equities like AAPL-USDC). Keep the active pair and any pair with unknown volume.
+ .filter((p) => volumes[p] !== 0 || p === value)
+ .sort((a, b) => {
+ const qr = quoteRank(a) - quoteRank(b);
+ if (qr !== 0) return qr;
+ // Unknown volume (not in the map, e.g. HIP-3 perps) sorts after known volume.
+ const va = volumes[a] ?? -1;
+ const vb = volumes[b] ?? -1;
+ if (vb !== va) return vb - va;
+ return a.localeCompare(b);
+ });
+ }, [rulesData, volData, value]);
+
+ const volumes = volData?.volumes ?? {};
// Group pairs by quote asset
const quoteGroups = useMemo(() => {
@@ -203,6 +249,11 @@ export function PairSelector({
}`}
>
{base}-{quote}
+ {volumes[p] > 0 && (
+
+ {formatVolume(volumes[p])}
+
+ )}
);
})
diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts
index a3dcaeaa..72ee9033 100644
--- a/frontend/src/lib/api.ts
+++ b/frontend/src/lib/api.ts
@@ -870,6 +870,11 @@ export const api = {
`/api/v1/servers/${server}/market/trading-rules?connector=${connector}`,
),
+ getPairVolumes: (server: string, connector: string) =>
+ apiFetch<{ connector: string; volumes: Record }>(
+ `/api/v1/servers/${server}/market/pair-volumes?connector=${connector}`,
+ ),
+
getOrderBook: (
server: string,
connector: string,