Skip to main content

Stock Tracker

A tactical, command-center style panel that tracks a single ticker: the latest price, its movement over a comparison window (~one week by default), a compact price chart, and supporting market fields. It runs against a pluggable market data provider and ships with a keyless demo (mock) provider, so it shows realistic data with no API key — seeded with Gold (XAU) and Silver (XAG), plus a few defense equities available from the picker.

At a glance

Type keystock-price
Default size4 × 14
Data sourceA ticker symbol (resolved by the market data provider)
Emits
Receives
Sourcesrc/widgets/widgets/stock-price/

Configuration

FieldTypeDefaultPurpose
symbolstring"XAU"Ticker to track, normalized to uppercase. Empty shows a setup prompt. In manual mode this is the label shown on the panel.
displayNamestring"Gold (Spot)"Friendly instrument name, cached from the picker.
exchangestring"COMEX"Listing venue, cached from the picker.
currencystring"USD"ISO 4217 quote currency.
modeenum"live"Where the numbers come from: live fetches from the market data provider, manual renders the values typed in manual with no network.
manualobject{ price: 0 }Hand-entered values used when mode is manual: price (required), previousClose (change baseline), and optional open/high/low/volume/avgVolume/marketCap for the metrics grid.
comparisonWindowDaysnumber (1–90)5Calendar window for the change; resolved to the closest trading day. The settings stepper caps this at 5 days to match the free-tier history.
refreshIntervalMinutesnumber (1–180)60How often the quote refreshes automatically. The stepper floors at 15 min; live prices are daily-delayed and cached for an hour.
showSparklinebooleantrueShow the compact price chart beneath the quote summary.
titlestringLabel for the panel header. Empty uses the tracked instrument name (or symbol), falling back to "Stock Tracker". Distinct from the card title.
showHeaderbooleantrueShow the panel header row (trend icon + title + refresh).
showRefreshbooleantrueShow the manual refresh button in the header.
compactbooleanfalseCompact mode: show only the header, symbol, and current price (with the change), hiding the chart, metrics grid, and footer.
compactLayoutenum"label-top"Arrangement of the symbol label relative to the price in compact mode: label-top, label-bottom, label-left, label-right.
showMetricsbooleantrueShow the supporting metrics grid (open/high/low/volume/avg vol/market cap).
showLastUpdatedbooleantrueShow the "Updated:" timestamp in the footer.
showSourcebooleantrueShow the exchange, market status, and provider attribution in the footer.

Config panel

  • Data — a Data source toggle (Live / Manual). In Live mode you pick the ticker (free-text, normalized to uppercase, with quick-pick chips for the seeded symbols), the comparison window, and the auto-refresh interval; picking a chip caches that symbol's name, exchange, and currency. In Manual mode you type the values directly — a label, currency, the headline Price and an optional Previous close (the change baseline), plus optional open/high/low/volume/avg volume/market cap for the metrics grid — and the widget renders them with no network access.
  • Appearance — a Show header toggle (and, when shown, a Refresh button toggle and a Header label that defaults to the tracked instrument), a Compact mode toggle with a label-position control, and — when not compact — toggles for the price chart, the metrics grid, the last-updated timestamp, and the source attribution. These are separate from the drawer's Show card header / Card title controls.

Cross-widget actions

None.

What it shows

  • Header — a trend icon, the panel title (the tracked instrument name or symbol unless a custom label is set), and a manual refresh control.
  • Primary summary — the dominant current price with its currency, and the comparison-window change (absolute + percent) with an up/down arrow. Positive movement tints green, negative red — text and icon only, no colored fills.
  • Price chart — a thin line with a subtle area fill over the recent history, with low-opacity grid lines, right-edge price labels, and date labels along the bottom (optional via showSparkline). It grows to fill the space freed by any hidden sections.
  • Metrics grid — open, high, low, volume, average volume, and market cap in a 2 × 3 grid with thin dividers (optional via showMetrics).
  • Footer — the exchange, a market-status dot/label, and the provider attribution on the left (showSource), and a last-updated timestamp on the right (showLastUpdated). The row collapses entirely when both are off.
  • Compact mode — when compact is on, the widget shows just the symbol label and the current price with its change, arranged per compactLayout. The text scales with the panel; hiding the header lets it grow larger to fill the reclaimed space.

States

  • Empty — when no symbol is set, a "Select a ticker to begin market tracking" prompt with a configure button.
  • Loading — the standard widget loader (a small spinner with a quiet "Loading" label), shared by every widget for a consistent feel.
  • Error — a compact "Market data unavailable for this symbol" message with a retry action; unknown symbols report a more specific not-found message.
  • Stale — when a refresh fails, the last good quote stays on screen and the footer timestamp is flagged as stale rather than blanking the panel.

Notable behavior

  • Pluggable provider — all data goes through a StockDataProvider contract (searchSymbols, getQuote, getHistoricalPrices). The default is the keyless mockStockDataProvider, which generates deterministic, symbol-seeded data. When a live API key is configured the widget uses real market data instead — no secrets are hard-coded, and the UI is provider-agnostic.
  • Manual entry — switching the Data source to Manual bypasses every provider: buildManualStock turns the entered values into the same normalized shape the live path produces, so the chart, change, metrics grid, and footer all render from hand-typed numbers. The change is measured against the entered previous close (one prior session), the refresh button is hidden, and the footer source reads "Manual entry".
  • Trading-day comparison — the change is measured against the session closest to comparisonWindowDays ago in trading days (a 5-day window ≈ a trading week), not an exact calendar offset.
  • Data isolation — network access and normalization live in stockDataProvider.ts; the React layer uses the useStockPriceData hook and never fetches directly.
  • Auto-refresh — the widget polls on refreshIntervalMinutes and supports manual refresh from the header. Rapid symbol edits are debounced into a single request.
  • Flexible layout — as optional sections are toggled off, the remaining elements expand to take up the available space rather than leaving gaps; the price chart absorbs most of the slack.

Live data (MetalpriceAPI)

By default the widget runs on the keyless mock provider, so it shows realistic data everywhere with no setup. To stream real metal and forex rates, set a MetalpriceAPI key:

# .env.local (frontend)
VITE_STOCK_API_KEY=your-metalpriceapi-key

With a key present, getStockProvider() selects the live createMetalPriceProvider(...) instead of the mock; with no key it falls back to the mock. Nothing else in the widget changes — the provider returns the same normalized shape.

Supported symbols

MetalpriceAPI covers precious metals (XAU, XAG, XPT, XPD, XRH), base metals, forex (150+ currencies), crypto, and energy — quoted against USD. The seeded Gold (XAU) and Silver (XAG) symbols work out of the box; you enter the bare metal code. It does not cover equities, so the defense-equity quick-picks are only available in mock mode.

Staying within the free tier

The MetalpriceAPI free tier serves daily-delayed rates on a monthly request quota. The live provider is built to stay well within that budget and to avoid showing errors during a live demo:

  • One request per refresh. The timeframe endpoint returns the entire daily series for the comparison window in a single call, and its latest point doubles as the current quote — no separate "latest" request. One cached series serves every range.
  • Shared cache + in-flight dedupe. Multiple widgets tracking the same symbol (and the parallel quote/history reads) collapse to one network request within the cache window (one hour by default).
  • Monthly quota guard. Every response carries X-API-QUOTA / X-API-CURRENT; as usage nears the cap the provider serves the last cached series instead of firing a request that would be rejected. A per-minute throttle smooths bursts.
  • Graceful staleness. If a refresh fails, the last good quote stays on screen and the footer is flagged stale rather than dropping to an error state.

Because the free tier is daily-delayed, frequent polling adds no freshness — the caches and quota guard mean a couple of distinct symbols comfortably fit a small monthly allowance, and duplicate widgets tracking the same symbol are free. The cache TTL and quota headroom live as constants at the top of metalPriceProvider.ts if you move to a paid plan. On the free tier the daily open/high/low mirror the close, so the metrics grid reflects the daily rate.

The free plan also caps timeframe history to a 5-day span, so the live provider fetches a 5-day window; longer comparison windows (e.g. 1‑month or 3‑month) gracefully fall back to the days available. Raise SERIES_CALENDAR_DAYS in metalPriceProvider.ts once you are on a paid plan for deeper history.