Skip to content
All projects

Financial data analytics · PWA · Data visualization

Cripto Terminal

A crypto terminal that shows whole-portfolio risk and explains every metric it computes

Private project — no public link

Client
Personal product
Role
Full-stack Software Engineer — independent project
Year
2026
Technologies
  • Next.js 16
  • React 19
  • TypeScript
  • Tailwind CSS 4
  • TanStack Query
  • Zustand
  • Zod
  • lightweight-charts
  • Recharts
  • Radix UI
  • Vitest

Context

Mobile-first progressive web app (PWA) for crypto-asset analysis, built with Next.js 16, React 19 and TypeScript on real market data from the CoinGecko and Binance public APIs. Personal use within a closed circle, behind an access code. The interface speaks like an analysis tool: it measures and describes, and does not recommend buying or selling.

Challenge

The free Portuguese-language tools I evaluated show prices and weights but not consolidated portfolio risk, and rarely state the formula, window or source of their indicators. Public market APIs enforce tight rate limits (HTTP 429), which calls for caching, rate control and stale-data signals to keep the interface usable.

Results

automated tests (Vitest) covering indicators, risk, portfolio and access
237
metrics documented with formula, window and pitfall
23
assets covered by the screener (the Market screen shows the top 100)
250
CoinGecko call per minute per currency for all 250 assets, with a cache shared across screens and users
1

Key deliverables

  1. 01Implemented in plain TypeScript, with Vitest tests, the 90-day portfolio risk panel (annualized volatility, Sharpe, Sortino, max drawdown, 95% VaR, beta relative to BTC, HHI and a correlation matrix), which brings consolidated portfolio risk into a single screen.
  2. 02Built candlestick charts with lightweight-charts, from 15 minutes to 1 week, with indicators computed in TypeScript (SMA 20, EMA 50, Bollinger Bands, RSI and MACD in panes) and a flagged fallback to CoinGecko daily candles when an asset has no Binance pair.
  3. 03Created a screener over the top 250 by market cap, with filters for market cap, 24h change, hourly RSI and trend, computing the indicators from the CoinGecko hourly sparkline: a single call per minute per currency covers all 250 assets.
  4. 04Designed the component library and design system with tokens in the Tailwind CSS 4 @theme, WCAG AA contrast and documented states (focus, error, empty, stale data, HTTP 429), reused across every screen together with Radix UI popovers and Recharts and SVG charts.
  5. 05Architected the data layer with Next.js Route Handlers, Zod validation, an LRU cache with dedupe and stale-on-error, per-IP rate limiting and HTTP 429 handling without retry storms, showing the data age in the interface.
  6. 06Secured the app with a per-request nonce CSP and an access-code gate with an HMAC-SHA256 cookie, limited to 5 attempts per IP every 15 minutes, and centralized the methodology of the 23 metrics in a single source that generates the popovers and the help page, with a test that fails after 12 months without review.
  1. Market

    The market screen lists the top 100 by market cap and shows in the header how long ago the data was updated.

    Market screen in dark theme: cards for global market cap, 24h volume, BTC dominance and active coins; lists of the top 24h gainers and losers; search by name or symbol; and the top 100 table with price, 1h, 24h and 7-day change, market cap, volume and a 7-day sparkline.
    Market screen in dark theme: cards for global market cap, 24h volume, BTC dominance and active coins; lists of the top 24h gainers and losers; search by name or symbol; and the top 100 table with price, 1h, 24h and 7-day change, market cap, volume and a 7-day sparkline.
  2. Chart with indicators

    The chart uses Binance candles and overlays SMA, EMA, Bollinger and MACD, computed in TypeScript inside the app.

    Bitcoin screen on the 4-hour interval: Binance candlestick chart with SMA 20, EMA 50, Bollinger Bands and volume, a MACD pane with histogram, and below it the technical read with RSI, MACD histogram and distance to the moving averages.
    Bitcoin screen on the 4-hour interval: Binance candlestick chart with SMA 20, EMA 50, Bollinger Bands and volume, a MACD pane with histogram, and below it the technical read with RSI, MACD histogram and distance to the moving averages.
  3. Portfolio and metrics on mobile

    The portfolio shows P&L, cost basis and allocation per asset. On mobile, the candlestick chart keeps the technical indicators, and each metric opens its formula, window and pitfall with one tap.

    • Fictitious portfolio on mobile: net worth, total P&L, today's change and cost in cards, an allocation donut across BTC, ETH, SOL, LINK and HYPE, and the start of the positions table.
      Fictitious portfolio on mobile: net worth, total P&L, today's change and cost in cards, an allocation donut across BTC, ETH, SOL, LINK and HYPE, and the start of the positions table.
    • Bitcoin on mobile: price up top, watchlist, alert and portfolio shortcuts, interval selector and a 1-hour candlestick chart with moving averages, volume and RSI.
      Bitcoin on mobile: price up top, watchlist, alert and portfolio shortcuts, interval selector and a 1-hour candlestick chart with moving averages, volume and RSI.
    • Coin screen on mobile with the RSI (14) popover open over the chart: formula, a window of 14 one-hour candles, how to read it, pitfall, Binance source and review date; below it, the technical read with RSI, MACD histogram and distance to the moving averages.
      Coin screen on mobile with the RSI (14) popover open over the chart: formula, a window of 14 one-hour candles, how to read it, pitfall, Binance source and review date; below it, the technical read with RSI, MACD histogram and distance to the moving averages.
  4. Portfolio risk

    The panel measures whole-portfolio risk, spells out the sample in chips and states that the tool does not recommend trades.

    90-day risk panel for a fictitious portfolio, below the end of the positions table: chips with window, number of returns, weights and benchmark; cards for annualized volatility, Sharpe, Sortino, max drawdown, 95% VaR, beta relative to BTC and HHI concentration; the full correlation matrix across BTC, ETH, SOL, LINK and HYPE; and the notice that the tool is not investment advice.
    90-day risk panel for a fictitious portfolio, below the end of the positions table: chips with window, number of returns, weights and benchmark; cards for annualized volatility, Sharpe, Sortino, max drawdown, 95% VaR, beta relative to BTC and HHI concentration; the full correlation matrix across BTC, ETH, SOL, LINK and HYPE; and the notice that the tool is not investment advice.
  5. Explained metrics

    The help page is generated from the same object that feeds the ⓘ icons across the screens.

    Help page with the methodology section: the RSI (14) entry is open and shows formula, windows, how to read it, pitfall, reference and data origin, followed by the other metrics collapsed.
    Help page with the methodology section: the RSI (14) entry is open and shows formula, windows, how to read it, pitfall, reference and data origin, followed by the other metrics collapsed.
  6. Watchlist and screener

    The watchlist tracks assets with sparklines and creates price alerts, and the screener filters 250 assets and states which series feeds the RSI.

    • Watchlist on mobile with five followed assets, each with a 7-day sparkline, price and 24h change, and the new price alert form with asset, condition and target price.
      Watchlist on mobile with five followed assets, each with a 7-day sparkline, price and 24h change, and the new price alert form with asset, condition and target price.
    • Screener on mobile: filters for market cap, 24h change, hourly RSI 14, trend and sort order, the note that RSI and trend come from CoinGecko's 7-day hourly series, and the start of the table over the 250-asset universe.
      Screener on mobile: filters for market cap, 24h change, hourly RSI 14, trend and sort order, the note that RSI and trend come from CoinGecko's 7-day hourly series, and the start of the table over the 250-asset universe.
  7. Foundations

    The foundations sheet gathers the Tailwind CSS 4 @theme tokens: color, contrast, typography, radii and chart palette.

    Cripto Terminal design system sheet 01, foundations: a surface and border ramp from background to border-strong with hex values, text colors with WCAG contrast computed against the background, semantic up, down, accent, cyan and violet colors with the 14% alpha soft variant, 14, 12, 8 and 6 px radii plus pill, a type scale with Inter and tabular JetBrains Mono, and the indicator and allocation donut palette.
    Cripto Terminal design system sheet 01, foundations: a surface and border ramp from background to border-strong with hex values, text colors with WCAG contrast computed against the background, semantic up, down, accent, cyan and violet colors with the 14% alpha soft variant, 14, 12, 8 and 6 px radii plus pill, a type scale with Inter and tabular JetBrains Mono, and the indicator and allocation donut palette.
  8. Component library

    The component library documents variants and states, including stale data and API rate limits (HTTP 429).

    Component library sheet 02: primary, secondary, ghost and danger buttons in default, hover, focus and disabled states; interval segmented control, USD/BRL toggle and indicator chips; form fields in default, focus and error with the Zod message; data states with the stale-data notice, the rate-limit (HTTP 429) error box with a retry button, the empty state and the confirmation toast; and change, trend, RSI range and caveat signals.
    Component library sheet 02: primary, secondary, ghost and danger buttons in default, hover, focus and disabled states; interval segmented control, USD/BRL toggle and indicator chips; form fields in default, focus and error with the Zod message; data states with the stale-data notice, the rate-limit (HTTP 429) error box with a retry button, the empty state and the confirmation toast; and change, trend, RSI range and caveat signals.
  9. Data visualization

    The data visualization components are reused across the coin, portfolio and screener screens.

    Data visualization sheet 04 with fictitious values: a candlestick chart card with SMA 20, EMA 50, Bollinger, volume and MACD; an allocation donut with a percentage legend; screener rows with change, RSI colored by range, trend and a 7-day sparkline; and a correlation matrix with a −1 to +1 scale.
    Data visualization sheet 04 with fictitious values: a candlestick chart card with SMA 20, EMA 50, Bollinger, volume and MACD; an allocation donut with a percentage legend; screener rows with change, RSI colored by range, trend and a 7-day sparkline; and a correlation matrix with a −1 to +1 scale.
  10. StatTile anatomy

    The StatTile and MetricInfo anatomy shows measurements, states and the single source of the methodology.

    Anatomy sheet 03: the Sharpe stat tile magnified two times with numbered pins for surface, radius and padding, label, info trigger, value and hint; the trigger states closed, hover, focus and open; the methodology popover with formula, window, how to read it, pitfall and source marked A to D; and the single-source flow from the metrics file to the popover, the help page and the review test.
    Anatomy sheet 03: the Sharpe stat tile magnified two times with numbered pins for surface, radius and padding, label, info trigger, value and hint; the trigger states closed, hover, focus and open; the methodology popover with formula, window, how to read it, pitfall and source marked A to D; and the single-source flow from the metrics file to the popover, the help page and the review test.
  11. Architecture

    The board follows one Market screen refresh from 1 to 12: the browser never calls CoinGecko or Binance, every quote goes through the functions in the gru1 region, with per-IP limits, a deduplicating cache and the previous data on failure, and the screen shows how old the data is.

    Cripto Terminal architecture board in the style of a Miro board, from tap to provider, with the figures 237 automated Vitest tests, 23 metrics with formula, window and pitfall, 250 assets covered by the screener, 1 call per minute per currency for all 250, at most 2 calls to the provider when the API is at its limit and 5 code attempts per IP every 15 min. Six zones: Client, the installable PWA with the Next.js 16 App Router screens, TanStack Query, the pure TypeScript analysis core that runs in the browser, persisted Zustand with validated rehydration, the AlertWatcher and the service worker; Vercel, with the proxy that checks the access cookie and issues the nonce-based CSP, the CDN, the SSR pages, the validated environment variables and the functions in the gru1 (São Paulo) region: handleRoute with a per-IP token bucket, an in-memory cache with dedupe and the previous data for up to 15 min on failure, CoinGecko and Binance clients validated with Zod, fetchJson with a timeout and controlled retries, sign-in, candles with fallback, Route Handlers and JSON logs; Data providers, the CoinGecko API v3 and the Binance spot API, which answers HTTP 451 to US IPs, which is why the functions run in gru1; CI/CD with ci.yml and deploys on push to main; Security, closed by default; and Decisions and trade-offs on sticky notes. The journey of one Market screen refresh is numbered 1 to 12, and flows A, B and C show code-based access, candles with fallback and delivery.
    Cripto Terminal architecture board in the style of a Miro board, from tap to provider, with the figures 237 automated Vitest tests, 23 metrics with formula, window and pitfall, 250 assets covered by the screener, 1 call per minute per currency for all 250, at most 2 calls to the provider when the API is at its limit and 5 code attempts per IP every 15 min. Six zones: Client, the installable PWA with the Next.js 16 App Router screens, TanStack Query, the pure TypeScript analysis core that runs in the browser, persisted Zustand with validated rehydration, the AlertWatcher and the service worker; Vercel, with the proxy that checks the access cookie and issues the nonce-based CSP, the CDN, the SSR pages, the validated environment variables and the functions in the gru1 (São Paulo) region: handleRoute with a per-IP token bucket, an in-memory cache with dedupe and the previous data for up to 15 min on failure, CoinGecko and Binance clients validated with Zod, fetchJson with a timeout and controlled retries, sign-in, candles with fallback, Route Handlers and JSON logs; Data providers, the CoinGecko API v3 and the Binance spot API, which answers HTTP 451 to US IPs, which is why the functions run in gru1; CI/CD with ci.yml and deploys on push to main; Security, closed by default; and Decisions and trade-offs on sticky notes. The journey of one Market screen refresh is numbered 1 to 12, and flows A, B and C show code-based access, candles with fallback and delivery.

Features and the pains they solve

Every feature was born from a real operational problem.

  1. 01

    Portfolio risk

    Pain
    Crypto investors track their holdings between the exchange app and a spreadsheet, and the free Portuguese-language tools I evaluated show each asset's price and weight but not the risk of the portfolio as a whole.
    Solution
    The panel fetches the 90-day daily series of each position from CoinGecko (up to 14 assets, plus BTC as the benchmark) and computes, in plain, tested TypeScript, annualized volatility, Sharpe, Sortino, max drawdown, 95% VaR, beta relative to BTC, HHI concentration and the correlation matrix. Chips spell out the assumptions, and the sample chip turns yellow below 60 returns.

    Impact: 7 measures over 90 days of daily returns, plus HHI concentration

  2. 02

    Positions and P&L

    Pain
    Without average price and live quotes in one place, P&L, cost basis and allocation depend on manual updates, and in an earlier version of the app an asset outside the top 250 was silently valued at 0.
    Solution
    Each position stores quantity, average price and the currency it was entered in; the screen computes net worth, P&L, today's change and an allocation donut. Assets outside the top 250 are priced separately, and a position without a price is flagged and left out of the risk calculation.

    Impact: No sign-up: quantities and average prices stay on the device

  3. 03

    Explained metrics (ⓘ and Help)

    Pain
    Market tools rarely state the window or the sample behind a number, and the documentation, when it exists, sits outside the chart and in English only.
    Solution
    A single typed file describes each metric (formula, real window, how to read it, pitfall, source, review date), importing the constants from the code itself, and generates the Radix popovers on the screens and the /ajuda help page. Tests fail on entries unreviewed for over 12 months and on recommendation verbs.

    Impact: 23 metrics with formula, window and pitfall

  4. 04

    Candlestick chart with indicators

    Pain
    Technical indicators usually show up without the window they were computed on, and hosted in the default US region the app wouldn't even get candles: Binance answers HTTP 451 to US servers.
    Solution
    Binance candles in lightweight-charts with SMA 20, EMA 50, Bollinger, RSI and MACD computed in TypeScript, and the technical read right below; the functions run in the São Paulo region (gru1). Assets without a Binance pair get daily candles synthesized from CoinGecko, flagged on screen.

    Impact: 5 intervals, from 15 minutes to 1 week

  5. 05

    Screener

    Pain
    Filtering by RSI and trend over the same universe as the portfolio would require candles for hundreds of assets, which would blow through the public API's rate limit.
    Solution
    Filters for market cap, 24h change, hourly RSI 14 and trend against the 24h SMA, with sorting, computed on the 7-day hourly sparkline that already comes with the CoinGecko listing. A note states which series the RSI comes from.

    Impact: The top 250 filtered with no extra calls

  6. 06

    Market

    Pain
    The market overview lives on CoinGecko, while the portfolio and the charts live in other tools.
    Solution
    The home screen brings together global market cap, 24h volume, BTC dominance, the top 24h gainers and losers, search and the top 100 table with 1h, 24h and 7-day change and a 7-day sparkline.

    Impact: Top 100 sliced from the same top-250 call

  7. 07

    Price alerts

    Pain
    Watching for a target price means checking quotes all the time, and a target set in dollars must not change value when the screen switches to reais.
    Solution
    An alert (above or below) stores the currency it was set in and is checked in that currency on every 60-second refresh while the Market or Watchlist screen is open and visible, converting through the exchange rate; without a rate, it is skipped. It fires once with a toast and a browser notification, if allowed, and re-arming keeps its history.

    Impact: Stores the last 20 triggers per alert (the screen lists the latest 3)

  8. 08

    USD and BRL

    Pain
    Crypto quotes come in USD while the portfolio may have been entered in BRL; without storing the original currency, switching the display currency distorts cost basis and P&L.
    Solution
    A USD/BRL toggle, saved on the device, requests CoinGecko quotes already in the chosen currency (Binance candles stay in USDT) and converts average prices through the implied exchange rate (global market cap in BRL ÷ in USD), documented with formula and pitfall on the Help page.
  9. 09

    Data age and API limits

    Pain
    Without a key, the CoinGecko API returns HTTP 429 after a few requests; the app showed the raw error, and a single refresh while rate-limited could turn into up to 9 calls to the provider.
    Solution
    Every call goes through the server, with a shared LRU cache (identical in-flight requests collapse into one, and the previous data is served on error), a per-IP limit (120 burst, 2 per second) and at most one retry on a 429. The Market screen shows "updated X min ago" and, when needed, the stale-data notice.

    Impact: When rate-limited, at most 2 calls to the provider per request

  10. 10

    Access and security

    Pain
    The tool is for personal use, with no accounts and a zero budget: left open on the internet, any script would burn the API quota, and an injected script could read the positions stored in the browser.
    Solution
    A proxy requires the code before any screen or API (Help and Privacy stay public), validated with Zod, compared in constant time and exchanged for an HttpOnly cookie signed with HMAC-SHA256, valid for 30 days. The same proxy emits a CSP with a per-request nonce and strict-dynamic, plus nosniff, X-Frame-Options and Referrer-Policy.

    Impact: 5 attempts per IP every 15 min (per-instance limit)