Earnings Calendar System
Overview
The earnings calendar system automatically fetches and displays upcoming earnings reports for all securities in the user’s portfolio, including manually tracked holdings, connected brokerage accounts, and SnapTrade positions.Architecture
Data Flow
- Cron Job (
/api/cron/fetch-earnings) runs weekly on Sundays at 3 AM UTC - Fetches all unique tickers from THREE sources:
securitiestable (Plaid-connected holdings)manual_public_holdingstable (manually tracked stocks)snaptrade_positionstable (SnapTrade brokerage positions)
- Deduplicates tickers using a Set to create a unique list
- Calls Finnhub API for each unique ticker to get earnings data for the next 365 days
- Upserts earnings data into the
earningstable - Frontend queries earnings based on user’s holdings
Components
Backend
/app/api/cron/fetch-earnings/route.ts: Cron job that fetches earnings data- Runs weekly:
0 3 * * 0(Sunday 3 AM UTC) - Max duration: 800 seconds
- Rate limited: 1 request per second (Finnhub free tier: 60 calls/min)
- Runs weekly:
Frontend
-
/hooks/queries/use-earnings.ts: React hook that fetches earnings for user’s holdings- Extracts ticker symbols from all holding types (manual, SnapTrade, connected)
- Queries earnings table for matching tickers
- Filters to show only upcoming/future earnings
-
/components/dashboard-earnings-card.tsx: Dashboard card showing next 4 upcoming earnings -
/app/earnings-calendar/page.tsx: Full earnings calendar page
Database Schema
API Integration
Finnhub API
- Endpoint:
https://finnhub.io/api/v1/calendar/earnings - Rate Limit: 60 calls/minute (free tier)
- Authentication: API key via
X-Finnhub-Tokenheader - Response: Array of earnings events with estimates and actuals
Environment Variables
FINNHUB_API_KEY: Required for API authenticationSUPABASE_URL: Supabase project URLSUPABASE_SERVICE_ROLE_KEY: Service role key for admin accessCRON_SECRET: Secret for authenticating cron requests
Holdings Support
The system supports earnings for all holding types, with both backend (data fetching) and frontend (display) integration:Manual Holdings
- Backend source:
manual_public_holdingstable (column:symbol) - Frontend structure:
securitiesis an object withticker_symbol - Includes only active holdings (
is_active = true)
SnapTrade Holdings
- Backend source:
snaptrade_positionstable (column:symbol) - Frontend structure:
securitiesis an array with first element containingticker_symbol - Positions from brokerage accounts (Schwab, Fidelity, etc.)
Connected Holdings (Plaid)
- Backend source:
securitiestable (column:ticker_symbol) - Frontend structure:
securitiesis an object withticker_symbol - Holdings from Plaid-connected bank/brokerage accounts
Deduplication
The backend cron job uses a JavaScriptSet to automatically deduplicate tickers across all three sources, ensuring each ticker is only fetched once from the Finnhub API even if a user has the same stock in multiple accounts.
Rate Limiting
The cron job implements rate limiting to respect Finnhub’s API limits:- 1 second delay between requests
- Processes securities sequentially (not parallel)
- Max 60 securities per minute
- Weekly schedule prevents excessive API usage
Error Handling
- Failed API calls are logged and counted
- Errors don’t stop the entire batch
- Response includes success/failure counts and error details
- Upsert logic prevents duplicate entries
Manual Trigger
To manually trigger the earnings fetch (for testing):Monitoring
Check cron job logs in Vercel dashboard:- Look for “Starting earnings data fetch…”
- Monitor success/error counts
- Check for Finnhub API rate limit errors
- Verify earnings data is being inserted
Future Enhancements
- Add earnings surprises (actual vs estimate)
- Send notifications for earnings reports
- Filter earnings by portfolio value/importance
- Add earnings call transcripts integration
- Historical earnings performance tracking
- Earnings impact on stock price analysis