DEV Community

NydarTrading
NydarTrading

Posted on • Originally published at nydar.co.uk

Intelligent Asset Bands: How We Rebuilt Symbol Navigation From Scratch

The Problem With Symbol Selectors

Every trading platform has a symbol selector. And almost every one of them is terrible.

You get a search box. Maybe a dropdown with some favourites. If you're lucky, there's a watchlist you can manually curate. But none of them actually help you find what's moving in the market right now. They're passive — they wait for you to already know what you want to look at.

That's backwards. A good trading platform shouldn't just display data for the symbol you picked. It should surface the symbols that matter right now and make switching between them instant.

Think about how a professional trader actually works. They're not staring at one chart all day. They're scanning. Flicking between symbols. Looking for momentum, volume spikes, sector rotations. The symbol selector isn't a minor UI element — it's the primary interface for discovery. And yet on most platforms it's an afterthought. A text input with autocomplete, maybe a star icon to save favourites. The same UX pattern we've had since the early 2000s.

Nydar used to have a search bar in the header. Type a symbol, hit enter, your dashboard updates. Functional. Boring. And fundamentally the same approach every other platform takes. You had to already know what you wanted to look at before you could look at it. If you wanted to find what was moving in crypto right now, you'd have to check an external screener, note the symbols, then come back to Nydar and type them in one by one.

We wanted something better. Something that turns symbol navigation from a passive lookup into an active discovery tool. Something that replaces the question "what do I want to look at?" with the answer "here's what's worth looking at right now."

That's what Asset Bands are.


What We Built

Asset Bands are horizontal strips that sit between the header and your dashboard. Each enabled asset class — crypto, stocks, forex — gets its own colour-coded band with a row of clickable symbol pills. Click any pill and your entire dashboard instantly reconfigures to show that symbol's data. Every chart, every indicator, every widget updates in real-time.

The visual design is intentional. Each asset class has its own accent colour: warm orange for crypto, electric blue for stocks, emerald green for forex. The left edge of each band has a solid accent border with a gradient wash that fades across the first 240 pixels. It's subtle enough that it doesn't compete with your charts, but distinctive enough that you can instantly tell which band you're looking at.

Each pill shows the symbol name and a small percentage badge showing the 24-hour change. Green for up, red for down. The active symbol is highlighted in the accent colour with a subtle glow. Inactive pills have a muted, dark appearance with soft borders — they look clickable without being visually noisy.

But the real power isn't in the pills themselves. It's in the filter system behind them.

Per-Asset Intelligent Filters

Each asset class gets its own set of filters, because what matters in crypto is completely different from what matters in equities or forex. We didn't want a one-size-fits-all dropdown where half the options are irrelevant. A crypto trader scanning for volume doesn't care about sector classifications. An equity trader looking at financials doesn't need a "Top 50 by market cap" filter — they know the names already.

Crypto filters:

  • Top 25 / Top 50 — ranked by actual market capitalisation from aggregated cross-exchange data, not an arbitrary hardcoded list
  • By Volume — the 25 highest-volume coins sorted by real 24-hour trading volume
  • Gainers — the top 25 biggest movers to the upside right now, sorted by percentage change
  • Losers — the top 25 biggest drops, for contrarian plays or risk monitoring
  • All — every tradeable pair on the exchange

Stock filters:

  • Top 25 — the blue-chip core
  • Gainers / Losers — sorted by real-time daily price change percentage
  • Tech Sector — AAPL, NVDA, MSFT, META, GOOGL, AMD, ADBE, CRM, and the rest of the tech heavyweights
  • Finance Sector — JPM, GS, BAC, V, MA, MS, WFC, C — Wall Street's core
  • All — the full universe of tracked equities

Forex filters:

  • Major Pairs — EUR/USD, GBP/USD, USD/JPY, USD/CHF, AUD/USD, NZD/USD, USD/CAD — the seven pairs that account for the vast majority of global forex volume
  • Cross Pairs — everything else, from EUR/GBP to AUD/JPY to CAD/JPY
  • All — the complete list of tracked pairs

The key word there is real. Every filter except the sector classifications is driven by live market data, not a static list someone typed into a config file six months ago. When you click "Gainers," you're seeing the actual top performers sorted by real price change data from the last 30 seconds. When you click "By Volume," you're seeing actual trading volume aggregated across exchanges. This isn't a screener that runs once a day and caches the results. It's live.


Why These Specific Filters?

We spent time thinking about what a trader actually needs when scanning markets. Not what looks impressive in a feature list — what actually changes behaviour.

Volume: The Most Underrated Signal

Volume is arguably the single most important piece of information a trader can have beyond price. High volume on an up-move suggests conviction — real money is flowing in, not just a few retail traders chasing. High volume on a down-move suggests capitulation or forced liquidation. Low volume on any move suggests it probably won't last.

The "By Volume" filter surfaces this immediately. During a major market event — an unexpected Fed announcement, a crypto exchange collapse, a surprise earnings beat — the volume filter instantly shows you where the action is. You don't need to check 50 charts. You don't need to scan a screener on another site. The symbols with the highest conviction are right there in front of you, ranked.

Gainers and Losers: Following Momentum (or Fading It)

Momentum traders want to see what's running. Contrarian traders want to see what's been beaten down. Both need the same data, sorted differently. The Gainers filter shows the biggest percentage moves to the upside over the last 24 hours. The Losers filter shows the biggest drops.

Crucially, these filters update with every price refresh cycle (every 30 seconds). A coin that was in the middle of the pack at 9am could be the top gainer by 10am. You'll see that shift in real-time because the filter re-sorts on every data update.

Sectors: Scanning an Industry in One Click

When a major economic event hits — an interest rate decision, a regulatory announcement, a trade policy change — it rarely affects just one stock. It moves entire sectors. The tech sector filter lets you see all tech stocks at once: are they all red, or is NVDA green while the rest are down? That's a signal. The finance sector filter shows you whether banks are moving together or diverging.

This is how institutional traders think. They don't look at individual stocks in isolation. They look at sectors, at correlations, at relative strength. The sector filters give retail traders the same lens.

Majors vs Crosses: Forex-Specific Logic

Forex is a different beast entirely. The seven major pairs all involve the US dollar and account for the overwhelming majority of global forex volume. Cross pairs don't involve the dollar and tend to have wider spreads and lower liquidity. Traders often specialise in one or the other, and the filter reflects that natural division.


The Data Pipeline That Makes It Work

Here's where the engineering gets interesting. Making filters that feel instant but are backed by real market data requires careful orchestration of multiple data sources, each with different strengths, different rate limits, and different refresh characteristics.

CoinGecko for Market Rankings

For crypto, we needed market capitalisation rankings, 24-hour volume figures, and price change percentages — and we needed them for the top 100 coins in a single API call. CoinGecko's /coins/markets endpoint is purpose-built for exactly this.

One request returns everything: current price, market cap, market cap rank, total volume, and 24-hour price change percentage. For 100 coins. In a single response. That's remarkably efficient — the kind of endpoint that was clearly designed by people who understood how trading platforms actually consume market data.

We cache this response for 10 minutes on the backend behind a dedicated cache key (ranked_crypto) in our API cache layer. Market cap rankings don't shuffle every second. If Bitcoin and Ethereum swap places in market cap, you'll see it within 10 minutes. That's more than fast enough for a ranking filter, and it means we're making roughly 144 CoinGecko API calls per day instead of thousands. Given CoinGecko's rate limits on their free tier, this matters.

The 10-minute TTL was a deliberate choice. Too short and we'd burn through rate limits. Too long and the "Gainers" filter would feel stale. Ten minutes means that during a volatile market day, you're never more than 10 minutes behind on rankings, but during a quiet day you're not wasting API calls fetching identical data.

Binance for the Long Tail

CoinGecko gives us the top 100 with rich metadata. But Nydar tracks hundreds of crypto pairs via Binance. The top 100 covers the majors, but there are traders who want to look at smaller-cap tokens that only show up on exchange-specific listings.

So after fetching ranked data from CoinGecko, we merge in the full Binance symbol list. The top 100 coins have market cap rankings, volume data, and change percentages baked in from the CoinGecko response. Everything beyond that gets basic symbol information from Binance — enough to show the pill and load the chart, but without the ranking metadata.

This two-tier approach means the "By Volume" and "Top 25" filters are driven by CoinGecko's aggregated cross-exchange data (which is more accurate than any single exchange's figures, since it represents global liquidity), while the "All" filter still shows every Binance pair for traders who want to go deep into the long tail.

The merge itself is careful not to create duplicates. We build a Set of symbols from the CoinGecko response, then filter the Binance list to exclude anything already covered. The result is a single symbol cache where the first ~100 entries are rich (with market cap, volume, rank) and the rest are basic (symbol and source only).

Live Price Updates Every 30 Seconds

Regardless of which data source populated the symbol list, every visible pill gets live price updates every 30 seconds via our bulk ticker endpoint. This serves two critical purposes.

First, it keeps the change percentage badges on each pill current. The tiny "+3.2%" next to a symbol isn't decorative — it's actionable information. If that number is stale, the entire filter system is stale. A 30-second refresh means the visual indicators are never more than half a minute behind reality.

Second, it means the Gainers and Losers filters are working with fresh data every time you switch filters. The filter function runs against the current symbol cache, and the symbol cache is updated every 30 seconds. So if you switch to "Gainers" at 10:00:15, you're seeing data from the 10:00:00 refresh at worst.

The update path is important here. When price data arrives from the ticker endpoint, we merge it into the existing symbol cache without replacing the CoinGecko metadata. So a coin keeps its market cap ranking (from CoinGecko, refreshed every 10 minutes) while its price and change percentage update every 30 seconds (from the live ticker feed). Two data sources, different refresh rates, seamlessly merged into a single reactive data structure.

This is handled at the Zustand store level. The updateSymbolPrices action takes a map of {symbol → {price, change24h, volume24h}} and patches each matching entry in the cache without touching fields like marketCapRank or marketCap. Zustand's immutable update pattern means React only re-renders the pills whose data actually changed — not the entire band.

Stock Data: Working Within Constraints

For stocks, the data situation is different. Finnhub's free tier gives us real-time quotes with price and daily change percentage, but no volume data and no sector classifications. That constrains what we can offer.

The Gainers and Losers filters work the same way as crypto — sort by change24h, take the top or bottom 25. The data comes from the same 30-second ticker refresh cycle.

For sector classification, we don't have a free API that returns sector data with price quotes. So we maintain a hardcoded sector map — tech and finance classifications for the ~36 stocks in our universe that fit those categories.

Is this ideal? No. We'd rather have a dynamic sector API. But the pragmatic reality is that Apple isn't going to stop being a tech company between API calls. The sector map changes perhaps once a year when a company restructures or when we add new stocks to our universe. Hardcoding it is the honest engineering choice rather than over-engineering a dynamic solution for data that's effectively static.

We deliberately chose tech and finance as the two sector filters because they represent the two largest and most traded sectors in our equity universe. Healthcare, energy, and consumer discretionary are candidates for future expansion as we grow the stock universe.

Forex: Convention Over Configuration

Forex pair classification is the simplest of the three. The seven major pairs (EUR/USD, GBP/USD, USD/JPY, USD/CHF, AUD/USD, NZD/USD, USD/CAD) are defined by global convention, not by any metric that changes. They're the pairs where both currencies are from major developed economies and one side is always USD. Everything else is a cross. We store the majors as a Set and classify by membership — if it's in the set, it's a major. If not, it's a cross.

This is the kind of thing that doesn't need to be clever. The seven major forex pairs haven't changed in decades. They won't change tomorrow. A Set lookup is the right answer.


The State Architecture

The entire filter system runs through a single Zustand store (assetBandStore) that manages state for all three asset classes simultaneously. The architecture decisions here were important for performance and persistence.

What Gets Persisted (and What Doesn't)

The store uses Zustand's persist middleware with a partialize function that carefully controls what gets saved to localStorage. User preferences — which bands are enabled, which filter is active, custom lists — those persist across sessions. If you set stocks to "Tech Sector" and close your browser, it's still on "Tech Sector" when you come back.

But the symbol cache and price data do not persist. They refresh on every page load. This is deliberate. Persisting a 100-item symbol cache with prices and volumes would mean showing stale data for the first few seconds of every session until the fresh data arrives. That's worse than showing nothing for a moment while data loads, because stale data looks real. A user might make a decision based on last Tuesday's volume data because it loaded instantly from localStorage. Instead, we show the known symbols immediately (from a hardcoded fallback list) and replace them with ranked data within a second or two.

Selective Re-renders

Each BandRow component subscribes to only its own slice of the store. The crypto band reads symbolCache['crypto'] and bands['crypto']. It doesn't subscribe to the stock or forex data. This means a price update for AAPL doesn't cause the crypto band to re-render. In a system where three asset classes are each updating every 30 seconds, this matters for keeping the UI responsive.

The useMemo on the filter application means the sorted/filtered list only recomputes when the underlying data actually changes — not on every render cycle. And the useCallback on the selection handler prevents unnecessary re-renders of the pill components.


The UX Details That Took Longer Than the Backend

The data pipeline was the intellectually satisfying part. But the UX polish is what actually makes Asset Bands usable, and it took significantly more iteration than the backend. We went through at least four major revisions of the interaction model before landing on something that felt right.

Wrong-Type Widget Handling: Three Attempts

Here's a problem we didn't anticipate until we started using Asset Bands ourselves: what happens when you have crypto-specific widgets (Order Flow, Volume Delta) on your dashboard and then click a stock symbol?

These widgets can only display crypto data — they depend on Binance WebSocket feeds that don't exist for equities. So when you switch to AAPL, they have nothing to show.

Attempt 1: Overlay messages. Our first solution was straightforward — show a message overlay: "This widget requires a crypto symbol. Select a crypto symbol above to view data." Technically correct. Visually horrifying. The crypto widgets became massive empty rectangles with a small text label floating in the middle. Your carefully arranged 6-widget dashboard suddenly had three giant voids in it. It looked broken, even though it was working exactly as designed.

Attempt 2: Auto-minimise in place. We collapsed mismatched widgets to a single-row height strip in their current grid position. Better — no more voids. But now you had thin collapsed bars scattered randomly across your dashboard, breaking the visual flow. A collapsed bar at row 2, column 3 created an awkward gap between the widgets above and below it.

Attempt 3: Auto-minimise to bottom. The final solution: when a widget's asset class doesn't match the selected symbol, it collapses to a single row and gets pushed to the bottom of the grid as a full-width bar. When you switch back to a matching symbol, it expands back to its original size and position.

This required solving a non-obvious problem with react-grid-layout's vertical compaction. Setting y: 999 on a widget pushes it down, but only within its current column. A 4-column-wide widget at x: 8 would sit at the bottom of column 8, potentially overlapping with full-height widgets in other columns.

The fix was making minimised widgets full-width (w: 12, x: 0). A full-width widget at y: 999 must go below everything else because it spans all columns. The grid's compaction algorithm has no choice but to place it at the very bottom.

We also needed to preserve the original dimensions for restoration. When a widget auto-minimises, we store its original height, width, x, y, and minWidth in the widget's persistent config object. When the mismatch resolves (user switches back to crypto), we read those values back and restore the widget to its exact previous position and size. This survives page reloads because the widget config persists through Zustand's localStorage layer.

There's also a flag (_autoMin) that distinguishes auto-minimised widgets from manually minimised ones. If you manually minimise a widget and then switch asset classes, we don't want the auto-expand to fight with your manual choice. The flag ensures that only widgets that were automatically collapsed get automatically restored.

Scroll Behaviour

Symbol bands can have 50+ pills. They need to scroll horizontally. This sounds simple but has several edge cases that affect usability.

We implemented three complementary scroll mechanisms:

  1. Mouse wheel — the scroll wheel maps to horizontal scroll. This is the expected behaviour for a horizontal list, but browsers don't do it automatically for horizontal overflow. We capture the onWheel event and translate deltaY into scrollLeft changes.

  2. Click and drag — you can grab the band and slide it laterally. This required careful momentum detection to distinguish between a click (which should select a symbol) and a drag (which should scroll the list). We track mouse movement from mousedown and only enter "drag mode" after 3 pixels of horizontal movement. If the user drags, we suppress the subsequent click event so it doesn't accidentally select a symbol.

  3. Edge fade — a CSS gradient overlay on the right edge signals that there are more pills to discover. It's a pointer-events-transparent div absolutely positioned over the scroll container, fading from the surface colour to transparent. Simple visual affordance, but without it users don't realise the band scrolls.

The Pill Design: Buttons, Not Labels

The symbol pills went through their own iteration. The first version looked like text labels — thin, no borders, no background. They didn't look clickable. Users hesitated before clicking them because the visual affordance was wrong.

We switched to button-like pills: visible borders (border-dark-700/30), a subtle background (bg-dark-800/25), rounded corners (rounded-md), and clear hover states that brighten the border and background. The active pill gets the full accent colour treatment — filled background, dark text, a soft glow shadow. The contrast between active (bright accent) and inactive (muted dark) is immediate and unambiguous.

The active:scale-95 on click gives tactile feedback — the pill shrinks slightly when pressed, like a physical button. The active pill gets scale-[1.04] so it sits slightly proud of its neighbours. These are tiny details, but they add up to something that feels intentional and polished rather than thrown together.

Filter Dropdown

The filter dropdown is deliberately minimal. A small button next to the asset label shows the current filter's short code. Click it and a dropdown appears with the available filters for that asset class.

Each filter option shows a human-readable label ("By Volume") and a short code ("Vol"). The active filter is highlighted with the asset's accent colour background. Click outside to dismiss. There are no animations, no transitions, no multi-level menus, no search, no icons.

We considered adding more visual flair to the dropdown — icons for each filter, descriptions, maybe a preview count. We stripped it all away. The dropdown appears, you make a choice, it disappears. The faster you can get through the filter selection, the faster you're back to looking at actual market data. Every millisecond spent in a dropdown menu is a millisecond not spent trading.


The Trading Workflow, Before and After

The real value of Asset Bands isn't any individual feature. It's the shift in mental model.

Before Asset Bands:

  1. Open Nydar
  2. Dashboard shows whatever symbol you looked at last
  3. If you want to check what's moving, open CoinGecko / Finviz / another screener in a new tab
  4. Find an interesting symbol on the external screener
  5. Switch back to Nydar
  6. Type the symbol in the search bar
  7. Wait for the dashboard to update
  8. Repeat for the next symbol

With Asset Bands:

  1. Open Nydar
  2. Glance at the bands — see what's green, what's red, what's moving
  3. Click a symbol — dashboard updates instantly
  4. Click "Gainers" — see the top performers right now
  5. Notice SOL is up 8% — click it — full analysis in front of you
  6. Switch to stocks — click "Tech" — scan the sector
  7. Notice NVDA is diverging from AMD — click between them to compare

That's a fundamentally different relationship between trader and platform. Instead of a tool that responds to your questions, it's a tool that prompts better questions. The information is pushed to you rather than pulled by you.

Professional traders at institutional desks have Bloomberg terminals that show exactly this kind of information — market movers, sector performance, volume leaders. It's constantly visible, constantly updating, and it shapes how they think about the market moment to moment. Asset Bands bring that same awareness to retail traders.


The Numbers

Some metrics on what went into this feature:

  • 6 files modified across frontend and backend
  • 3 data sources orchestrated (CoinGecko, Binance, Finnhub) with different refresh rates and cache strategies
  • 15 distinct filters across three asset classes, each backed by real market data
  • 30-second price refresh cycle on every visible pill
  • 10-minute ranking cache from CoinGecko, reducing API calls from thousands to ~144/day
  • 5 widget dimensions stored and restored during auto-minimise (height, width, x, y, minWidth)
  • 3 scroll mechanisms (wheel, drag, edge fade)
  • 4 major UX revisions before landing on the final interaction model

What's Next

Asset Bands are live now. If you're a Nydar user, you'll see them between the header and your dashboard. The crypto and stock bands are enabled by default; forex is available in the asset class toggles in the header.

We're planning to add custom watchlists — the ability to create your own symbol groups and save them as a filter. The architecture already supports it. There's a custom filter type in the store, a full custom list management system with create/delete/add/remove operations, and the filter function already handles the custom case. We just need to build the UI for creating and managing lists — a modal to name the list, a way to add symbols to it, and a way to select it from the filter dropdown.

We're also looking at adding more sector classifications for stocks as we expand the equity universe. Healthcare and energy are the obvious next additions. And potentially a "trending" filter for crypto that surfaces coins seeing unusual search or social activity — CoinGecko has a trending endpoint that could power this.

The longer-term vision is for Asset Bands to become a genuine market scanning tool. Not just a way to switch symbols, but a way to discover trading opportunities. Real-time anomaly detection — flagging when a coin's volume spikes 5x its average, or when an entire sector moves in the opposite direction of the market. Correlation alerts — when two usually-correlated symbols diverge. Regime change detection — surfacing when the market transitions from trending to ranging.

For now, try switching between the filters. Click "Gainers" on a red day. Look at "By Volume" during a major market event. Switch between Tech and Finance when rate decisions drop. You'll start noticing things you would have missed with a search box.

That's the whole point.


Originally published at Nydar. Nydar is a free trading platform with AI-powered signals and analysis.

Top comments (0)