Posts

Daily Interactive Brokers Margin Rates Tracker: Building with Cloudflare Workers, D1, and AI

March 9, 2026

Interactive Brokers publishes margin rates on their website, but there's no API or historical data. I built an automated system that scrapes rates daily and visualizes them with interactive charts, running entirely on Cloudflare's edge platform.

Live Margin Rates Dashboard

Loading margin rates...

The Challenge

IBKR margin rates change frequently based on benchmark rates and vary across:

  • 8+ currencies (USD, EUR, GBP, CAD, CHF, JPY, etc.)
  • Multiple tiers based on loan amount
  • Two account types (IBKR Pro vs IBKR Lite)

Without historical data, it's impossible to track trends or compare rates over time.

Architecture

The solution uses four Cloudflare services in a pipeline:

Cron Trigger (daily 9 AM UTC)
    ↓
Browser Rendering API (renders JavaScript page)
    ↓
Workers AI (extracts structured data from HTML)
    ↓
D1 Database (stores time-series data)
    ↓
Next.js SSR/ISR (serves interactive charts)

Implementation

1. Browser Rendering for JavaScript Pages

IBKR's margin rates page is JavaScript-heavy. A simple fetch() returns incomplete HTML. Cloudflare's Browser Rendering API runs a headless browser that executes JavaScript before returning the fully-rendered HTML.

2. AI-Powered HTML Parsing

Instead of brittle regex or DOM parsing, I use Workers AI with GLM-4.7-Flash to extract structured data:

const prompt = `Extract margin rate tiers from this HTML table.
Return JSON array with: currency, tierName, tierMin, tierMax, rate

Example: [{"currency":"USD","tierName":"100K-1M","tierMin":100000,"tierMax":1000000,"rate":4.64}]`;

const response = await env.AI.run("@cf/zai-org/glm-4.7-flash", {
  messages: [
    { role: "system", content: "Extract structured data from HTML tables." },
    { role: "user", content: prompt + html },
  ],
  max_tokens: 8192,
});

Why GLM-4.7-Flash?

  • Fast inference with reasoning capabilities
  • Handles complex HTML structure variations
  • Returns both data and step-by-step reasoning

Before sending to AI, I extract only the relevant table section (12KB) from the full HTML (220KB), reducing token costs by around 95%.

3. Storage in D1 Database

The extracted data is stored in Cloudflare D1 (SQLite) with this schema:

CREATE TABLE margin_rates
(
    date             TEXT NOT NULL,
    currency         TEXT NOT NULL,
    account_type     TEXT NOT NULL,
    tier_name        TEXT NOT NULL,
    tier_min         REAL,
    tier_max         REAL,
    rate             REAL NOT NULL,
    benchmark_margin REAL
);

CREATE INDEX idx_date_currency ON margin_rates (date, currency);

4. Automated Daily Updates

Cloudflare Workers cron triggers run the scraper daily:

export default {
  async scheduled(event: ScheduledEvent, env: Env) {
    const rates = await fetchMarginRates(
      env.AI,
      env.CLOUDFLARE_ACCOUNT_ID,
      env.CLOUDFLARE_API_TOKEN,
    );

    await storeRates(env.FINANCE, new Date(), rates);
  },
};

5. Interactive Visualization

The Next.js page is generated on the first request on Cloudflare and then revalidated every 12 hours. The chart component uses Recharts with three view modes:

  • Current rates comparison across currencies
  • Historical trends (90 days)
  • Pro vs Lite account comparison