Portfolio Tracking

The Portfolio page gives you a consolidated view of everything you hold across all your accounts -- Paper and exchange alike -- valued in both USD and BTC, updated automatically over time.

What Is Portfolio Tracking?

Portfolio tracking answers a simple question: how much is everything worth, right now and over time?

Botmarley periodically takes a "snapshot" of your portfolio. Each snapshot records:

  • The total value in USD across all accounts.
  • The total value in BTC (your USD total divided by the current BTC price).
  • The BTC/USD price at that moment.
  • A per-token breakdown showing balance and USD value for each asset.

By storing snapshots over time, Botmarley can draw charts showing how your portfolio value has changed -- and whether you are gaining or losing ground relative to Bitcoin itself.

How It Works

Portfolio syncing follows a pipeline that runs on a fixed schedule and can also be triggered manually.

Automatic sync schedule

A background scheduler starts when the Botmarley server boots. After an initial 60-second startup delay, it enqueues a PortfolioSync task every 1 hour. The task worker picks it up and runs the full sync pipeline.

The sync pipeline

flowchart LR
    A["Scheduler\n(every 1h)"] -->|enqueues task| B["Task Worker"]
    B --> C["Sync Exchange\nAccounts"]
    C --> D["Query All\nAccount Assets"]
    D --> E["Fetch USD Prices\n(Kraken Ticker API)"]
    E --> F["Compute Totals\n(USD + BTC)"]
    F --> G["Store Snapshot\n(PostgreSQL)"]

In detail, each sync performs these steps:

  1. Sync verified exchange accounts -- for every exchange account with status "Verified", Botmarley calls the exchange API to pull the latest balances. Failed syncs for individual accounts are logged but do not stop the pipeline.
  2. Query all account assets -- reads every non-zero asset balance across all accounts (including Paper) from the database.
  3. Fetch USD prices -- calls the Kraken public Ticker API to get current USD prices for each unique token. Stablecoins and fiat are handled locally (see below).
  4. Compute totals -- sums up balance * price for every asset to get total_value_usd. Fetches the BTC/USD price separately and calculates total_value_btc = total_value_usd / btc_price.
  5. Store snapshot -- inserts one aggregate row (with account_id = NULL) into the portfolio_snapshots table, including a JSON breakdown of per-token values.

The Portfolio Page

The portfolio page is located at /portfolio. It has several sections, described from top to bottom.

Portfolio page showing summary cards, value charts, and per-token asset breakdown

Summary Cards

Four stat cards appear at the top of the page:

CardDescriptionExample
Total Value (USD)Sum of all assets across all accounts, converted to USD.$12,345.67
Total Value (BTC)Your USD total divided by the current BTC price.0.142857
BTC PriceThe latest BTC/USD price fetched from Kraken.$86,420.00
Last SyncTimestamp of the most recent portfolio snapshot.2026-03-11 14:00 UTC

If no snapshots exist yet (fresh install), the page shows an empty state with buttons to manage accounts or run a first sync.

Range Selector

Below the summary cards, a row of small buttons lets you control the time range for the charts:

ButtonTime range
7dLast 7 days
30dLast 30 days (default)
90dLast 90 days
AllAll available history

Switching ranges fetches new data from /api/portfolio/chart?range=... and updates both charts without a full page reload. A small label next to the buttons shows how many data points are in the current view.

USD Chart: Portfolio Value + BTC Price Overlay

The first chart displays two lines on a dual-axis layout:

  • Portfolio USD (blue, right axis) -- your total portfolio value over time in US dollars.
  • BTC Price (amber/dashed, left axis) -- the Bitcoin price over the same period.

This overlay lets you visually compare whether your portfolio is outperforming or underperforming a simple Bitcoin hold. The charts are rendered using Lightweight Charts loaded from a CDN.

BTC Chart: Portfolio Value in BTC

The second chart shows a single amber line: your portfolio's value expressed in Bitcoin. If this line trends upward, you are accumulating more BTC-equivalent value over time, even if the USD value moves sideways.

Asset Breakdown Table

Below the charts, a table lists every token you hold across all accounts:

ColumnDescription
TokenThe asset symbol (BTC, ETH, USDC, etc.)
BalanceTotal holdings of that token across all accounts.
USD ValueBalance multiplied by the current USD price.
Account(s)Which accounts hold this token and how much each one holds.

The table includes a Refresh button that fetches the latest data via HTMX without reloading the page. Assets are sorted by USD value in descending order, so your largest holdings appear first.

Manual Sync

You do not have to wait for the hourly scheduler. To trigger a portfolio sync on demand:

  1. Go to the Portfolio page (/portfolio).
  2. Click the Sync Now button in the top-right corner.
  3. Botmarley enqueues a PortfolioSync task and redirects you back to the portfolio page.
  4. The task worker processes the sync (usually completes within a few seconds).
  5. Refresh the page to see updated values and charts.

Tip

After adding a new exchange account and verifying it, click "Sync Now" on the Portfolio page to immediately pull in its balances rather than waiting for the next scheduled sync.

How Prices Are Fetched

Botmarley uses the Kraken public Ticker API to fetch USD prices. This endpoint requires no authentication and has generous rate limits.

The API call

A single request is made to https://api.kraken.com/0/public/Ticker?pair=XXBTZUSD,XETHZUSD,... with all needed trading pairs joined by commas. The response includes the last trade price for each pair, which Botmarley uses as the current price.

Token-to-pair mapping

Botmarley maps normalized token names to Kraken trading pair identifiers:

TokenKraken Pair
BTC / XBTXXBTZUSD
ETHXETHZUSD
LTCXLTCZUSD
XRPXXRPZUSD
XLMXXLMZUSD
(others){TOKEN}USD

For tokens that Kraken does not list, the price falls back to 0.0 and a warning is logged.

Stablecoin Handling

Stablecoins and fiat currencies are handled without an API call:

TokensAssumed USD price
USD, USDC, USDT, BUSD, DAI, TUSD, USDP, GUSD$1.00
EUR, GBP, CAD, AUD, CHF, JPY~$1.00 (rough approximation)

Note

The fiat approximation (treating EUR, GBP, etc. as $1.00) is a simplification. For portfolios with significant fiat holdings in non-USD currencies, the total USD value will be approximate. A more accurate forex conversion may be added in a future release.

Architecture: Full Sync Flow

The following diagram shows the complete lifecycle of a portfolio sync, from trigger through to the stored snapshot.

sequenceDiagram
    participant S as Scheduler (1h) /<br>Manual Button
    participant Q as Task Queue
    participant W as Task Worker
    participant K as Kraken API
    participant DB as PostgreSQL

    S->>Q: Enqueue PortfolioSync task
    Q->>W: Dequeue task

    Note over W: Step 1: Sync exchange accounts
    W->>DB: List verified exchange accounts
    DB-->>W: [Kraken accounts]
    loop Each verified account
        W->>K: POST /0/private/Balance (signed)
        K-->>W: Token balances
        W->>DB: Upsert account_assets
    end

    Note over W: Step 2: Gather all assets
    W->>DB: SELECT token, balance FROM account_assets
    DB-->>W: All non-zero balances

    Note over W: Step 3: Fetch prices
    W->>K: GET /0/public/Ticker?pair=...
    K-->>W: USD prices per pair

    Note over W: Step 4-5: Compute and store
    W->>W: total_usd = SUM(balance * price)<br>total_btc = total_usd / btc_price
    W->>DB: INSERT INTO portfolio_snapshots
    DB-->>W: Snapshot ID

    W->>Q: Mark task complete

Note

If a single exchange account fails to sync (e.g., temporary network error), the pipeline continues with the remaining accounts. The snapshot will still be created using whatever data was successfully retrieved. Check the Activity Logs or Task Queue for details on any failures.