# Economic Calendar Integration

**Shipped:** 2026-05-08

## Summary

Unified economic calendar data from 3 sources:
- **Finnhub** - Economic calendar via existing API key (free tier)
- **FXStreet** - Comprehensive calendar with OAuth2 (enterprise API)
- **FRED** - Release dates for major economic indicators

## Architecture

```
┌──────────────────────────────────────────────────────────────────┐
│                     Economic Calendar System                      │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  Data Sources                                                    │
│  ┌─────────┐   ┌─────────┐   ┌─────────┐                        │
│  │ Finnhub │   │ FXStreet│   │  FRED   │                        │
│  │ API Key │   │ OAuth2  │   │ API Key │                        │
│  └────┬────┘   └────┬────┘   └────┬────┘                        │
│       │             │             │                              │
│       ▼             ▼             ▼                              │
│  ┌──────────────────────────────────────┐                       │
│  │     Unified Calendar Service         │                       │
│  │  packages/be/src/economic-calendar/  │                       │
│  └────────────────┬─────────────────────┘                       │
│                   │                                              │
│                   ▼                                              │
│  ┌──────────────────────────────────────┐                       │
│  │    predict.economic_calendar_events   │                       │
│  │          (PostgreSQL)                 │                       │
│  └────────────────┬─────────────────────┘                       │
│                   │                                              │
│                   ▼                                              │
│  ┌──────────────────────────────────────┐                       │
│  │         API Endpoints                 │                       │
│  │  /api/predict/v1/economic-calendar/*  │                       │
│  └────────────────┬─────────────────────┘                       │
│                   │                                              │
│                   ▼                                              │
│  ┌──────────────────────────────────────┐                       │
│  │         /economic-calendar            │                       │
│  │      (Calendar & List Views)          │                       │
│  └──────────────────────────────────────┘                       │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘
```

## Database Schema

### Migration 191

```sql
-- predict.economic_calendar_events
CREATE TABLE predict.economic_calendar_events (
  id UUID PRIMARY KEY,
  source TEXT NOT NULL,          -- 'finnhub', 'fxstreet', 'fred'
  source_event_id TEXT,          -- External ID for deduplication
  event_name TEXT NOT NULL,
  country TEXT NOT NULL,         -- ISO 3166-1 alpha-2
  event_date TIMESTAMPTZ NOT NULL,
  actual_value NUMERIC,
  forecast_value NUMERIC,
  previous_value NUMERIC,
  unit TEXT,                     -- '%', 'K', 'M', 'B', 'index'
  impact TEXT,                   -- 'low', 'medium', 'high', 'critical'
  category TEXT,                 -- 'employment', 'inflation', 'gdp', etc.
  currency TEXT,                 -- USD, EUR, GBP, etc.
  is_released BOOLEAN,
  surprise_pct NUMERIC,          -- (actual - forecast) / |forecast| * 100
  metadata JSONB,
  created_at TIMESTAMPTZ,
  updated_at TIMESTAMPTZ,
  UNIQUE (source, source_event_id)
);

-- FXStreet OAuth token storage
CREATE TABLE predict.fxstreet_oauth_tokens (
  id SERIAL PRIMARY KEY,
  access_token TEXT NOT NULL,
  token_type TEXT NOT NULL,
  expires_at TIMESTAMPTZ NOT NULL,
  scope TEXT,
  created_at TIMESTAMPTZ,
  updated_at TIMESTAMPTZ
);

-- FRED release schedule cache
CREATE TABLE predict.fred_release_schedule (
  id UUID PRIMARY KEY,
  release_id INTEGER NOT NULL,
  release_name TEXT NOT NULL,
  release_date TIMESTAMPTZ NOT NULL,
  series_ids TEXT[],
  created_at TIMESTAMPTZ,
  UNIQUE (release_id, release_date)
);
```

## API Endpoints

### GET /api/predict/v1/economic-calendar/events

Get upcoming economic events with filters.

**Query Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `country` | string | ISO country code (US, EU, GB, JP, etc.) |
| `countries` | string | Comma-separated country codes |
| `impact` | string | Impact level(s): low, medium, high, critical |
| `category` | string | Category: employment, inflation, gdp, rates, housing, trade, consumer, manufacturing |
| `currency` | string | Currency code: USD, EUR, GBP, JPY, etc. |
| `source` | string | Data source: finnhub, fxstreet, fred |
| `startDate` | string | Start date (YYYY-MM-DD) |
| `endDate` | string | End date (YYYY-MM-DD) |
| `limit` | number | Max results (default 50, max 100) |
| `offset` | number | Pagination offset |

**Response:**
```json
{
  "events": [
    {
      "id": "uuid",
      "source": "finnhub",
      "eventName": "Nonfarm Payrolls",
      "country": "US",
      "eventDate": "2026-05-08T12:30:00Z",
      "actualValue": 180000,
      "forecastValue": 175000,
      "previousValue": 165000,
      "unit": "K",
      "impact": "high",
      "category": "employment",
      "currency": "USD",
      "isReleased": true,
      "surprisePct": 2.86
    }
  ],
  "pagination": {
    "total": 150,
    "limit": 50,
    "offset": 0,
    "hasMore": true
  }
}
```

### GET /api/predict/v1/economic-calendar/releases

Get recent releases with surprise data.

**Query Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `hours` | number | Hours to look back (default 24) |
| `country` | string | Filter by country |
| `impact` | string | Filter by impact level |
| `currency` | string | Filter by currency |
| `limit` | number | Max results (default 20, max 50) |

**Response:**
```json
{
  "releases": [...],
  "recentCount": 12,
  "hoursAgo": 24
}
```

### POST /api/predict/v1/admin/economic-calendar/sync

Manual sync trigger (admin only).

**Response:**
```json
{
  "success": true,
  "result": {
    "finnhub": { "inserted": 45, "updated": 12, "errors": 0 },
    "fxstreet": { "inserted": 0, "updated": 0, "errors": 0 },
    "fred": { "inserted": 8, "updated": 2, "errors": 0 },
    "totalInserted": 53,
    "totalUpdated": 14,
    "durationMs": 2340
  }
}
```

## Scheduler Jobs

### economic-calendar-sync

- **Interval:** Every 6 hours
- **Purpose:** Syncs events from all 3 sources
- **Range:** Past 7 days to next 30 days

```typescript
// packages/be/src/scheduler/index.ts
registerJob('economic-calendar-sync', 6 * 60 * 60 * 1000, async () => {
  const r = await syncEconomicCalendar();
  console.log(`Economic calendar: ${r.totalInserted} inserted, ${r.totalUpdated} updated`);
});
```

## Data Sources

### Finnhub (Primary)

Already integrated via `FINNHUB_API_KEY`. The economic calendar endpoint is included in the free tier (60 calls/min).

```typescript
// packages/be/src/integrations/finnhub.ts
export async function getEconomicCalendar(
  from: string,  // YYYY-MM-DD
  to: string
): Promise<FinnhubEconomicEvent[]>
```

### FXStreet (Optional)

OAuth2 client credentials flow. Requires enterprise API access.

**Environment Variables:**
```bash
FXSTREET_CLIENT_ID=your-client-id
FXSTREET_CLIENT_SECRET=your-client-secret
```

**Token Management:**
- Tokens stored in `predict.fxstreet_oauth_tokens`
- Auto-refresh when token expires or nears expiry
- Platform-level (not per-user) credentials

```typescript
// packages/be/src/integrations/fxstreet/client.ts
export async function getAccessToken(): Promise<string | null>
export async function fxstreetFetch<T>(endpoint: string): Promise<T | null>
```

### FRED Release Dates

Leverages existing `FRED_API_KEY` to fetch release schedules for key indicators.

**Tracked Releases:**
| Release ID | Name | Series |
|------------|------|--------|
| 50 | Employment Situation | UNRATE, PAYEMS |
| 10 | Consumer Price Index | CPIAUCSL, CPILFESL |
| 53 | Gross Domestic Product | GDP, GDPC1 |
| 115 | FOMC Minutes | FEDFUNDS, DFF |
| 328 | Federal Funds Rate | FEDFUNDS |
| 46 | Producer Price Index | PPIACO |
| 45 | Advance Retail Sales | RSXFS |
| 47 | Industrial Production | INDPRO |
| 33 | Housing Starts | HOUST |
| 86 | Durable Goods Orders | DGORDER |

## UI Components

### /economic-calendar

Main calendar page with:
- **Calendar View** - Monthly grid with event density indicators
- **List View** - Chronological event list with filters
- **Filters** - Country, impact, category, currency, source
- **Upcoming Events** - Sidebar widget for high-impact events
- **Recent Releases** - Last 24h releases with surprise data

### Components

```typescript
// packages/fe/src/components/economic-calendar/
export { CalendarGrid } from './CalendarGrid';     // Monthly calendar grid
export { EventCard } from './EventCard';           // Event display card
export { EventFilters } from './EventFilters';     // Filter controls
export { UpcomingEvents } from './UpcomingEvents'; // Sidebar widget
```

## Integration Registry

FXStreet added to `/admin/integrations`:

```typescript
{
  id: 'fxstreet',
  name: 'FXStreet Economic Calendar',
  category: 'macro',
  provides: 'Global economic calendar with forecasts, actuals, impact ratings',
  envVars: ['FXSTREET_CLIENT_ID', 'FXSTREET_CLIENT_SECRET'],
  envVarsAllRequired: true,
  freeTier: false,
  signupUrl: 'https://docs.fxstreet.com/',
  testEndpoint: '/api/predict/v1/economic-calendar/events?source=fxstreet&limit=5',
  notes: 'OAuth2 API. Contact FXStreet for API access credentials.'
}
```

## File Structure

```
packages/be/src/
├── economic-calendar/
│   ├── types.ts         # Type definitions
│   ├── repository.ts    # Database operations
│   └── service.ts       # Sync and query logic
├── integrations/
│   ├── finnhub.ts       # +getEconomicCalendar()
│   └── fxstreet/
│       ├── types.ts     # FXStreet types
│       ├── client.ts    # OAuth2 client
│       ├── calendar.ts  # Calendar API
│       └── index.ts     # Barrel export
├── overlays/
│   └── data-fetcher.ts  # +fetchFREDReleaseDates()
└── scheduler/
    └── index.ts         # +economic-calendar-sync job

packages/fe/src/components/economic-calendar/
├── types.ts
├── EventCard.tsx
├── EventFilters.tsx
├── CalendarGrid.tsx
├── UpcomingEvents.tsx
└── index.ts

apps/web/src/app/
├── economic-calendar/
│   └── page.tsx
└── api/predict/v1/
    ├── economic-calendar/
    │   ├── events/route.ts
    │   └── releases/route.ts
    └── admin/economic-calendar/
        └── sync/route.ts

db/migrations/
└── 191_economic_calendar.sql
```

## Deployment

1. **Apply migration:**
   ```bash
   psql $DATABASE_URL < db/migrations/191_economic_calendar.sql
   ```

2. **Configure FXStreet (optional):**
   ```bash
   # Add to Vercel environment
   FXSTREET_CLIENT_ID=xxx
   FXSTREET_CLIENT_SECRET=xxx
   ```

3. **Verify integration:**
   - Check `/admin/integrations` for FXStreet status
   - Hit test endpoint: `GET /api/predict/v1/economic-calendar/events?limit=5`
   - Manual sync: `POST /api/predict/v1/admin/economic-calendar/sync`

4. **Scheduler:**
   - `economic-calendar-sync` job starts automatically
   - Runs every 6 hours
   - First sync runs on scheduler boot

## Notes

- **Investing.com excluded** - No public API available (contractual restrictions)
- **FXStreet** requires enterprise API access - contact them for credentials
- **Finnhub** economic calendar is included in free tier (60 calls/min)
- **FRED** release dates are free with existing API key
- Surprise % calculated as `(actual - forecast) / |forecast| * 100`
- Events are deduplicated by `(source, source_event_id)` UNIQUE constraint
