A Streamlit dashboard demonstrating resilient patterns for integrating multiple APIs with caching, retry logic, and graceful error handling.
- Demo
- Overview
- Features
- Resilient Design Patterns
- Architecture
- Prerequisites
- Installation
- Running the Application
- Background Refresh Mode
- Testing
- Project Structure
- Configuration
- Troubleshooting
- License
Demo showing real-time stock data, news feed, and AI-generated insights
This financial dashboard aggregates real-time stock data, news articles, and AI-generated insights for Netflix (NFLX), Alphabet (GOOGL), and Tesla (TSLA). Built to demonstrate resilient API integration patterns with smart caching and graceful degradation.
Disclaimer: This application is for educational and demonstration purposes only. It does not provide financial, investment, legal, or professional advice. Stock data and AI-generated insights should not be used as the basis for any investment decisions. Always consult with qualified financial professionals before making investment decisions. The developers are not responsible for any financial losses or damages resulting from the use of this application.
- Multi-API integration with Polygon.io, NewsDataHub, and OpenAI
- Smart caching with configurable TTL and stale data fallback
- Retry logic with exponential backoff (429 rate limits: 15s/30s/45s, other errors: 0.5s/1s/2s)
- Graceful degradation when APIs fail
- DRY refactoring with generic
_fetch_with_cache()method - Independent component loading for better UX
- Two deployment modes (local interactive / background refresh for predictable API costs)
- Stock Prices - Real-time pricing and 1-month historical charts via Polygon.io (now Massive.com)
- Related Stocks - Competitor stock prices with daily percentage changes
- News Articles - Curated financial news from mainstream sources via NewsDataHub
- AI Insights - GPT-powered analysis combining price trends and news via OpenAI
- Independent component loading - Each section renders with separate loading states
- Async API calls - Non-blocking HTTP requests using
httpx.AsyncClient - Retry decorator - Handles 429 rate limits (15s/30s/45s delays), 5xx/timeouts (0.5s/1s/2s delays)
- Fresh vs. stale caching -
get_fresh()enforces TTL,get_stale()for fallback - JSON-based caching - Human-readable cache files with automatic cleanup
- Dynamic chart scaling - Y-axis adjusts to actual price range
- Brand colors - Company-specific colors for tickers
API clients use @retry_with_backoff decorator to handle transient failures:
@retry_with_backoff(retry_on=(httpx.HTTPError,))
async def get_stock_data(self, ticker: str) -> dict:
# Automatic retry on:
# - 429 (rate limit) → 15s, 30s, 45s delays
# - 5xx (server errors) → 0.5s, 1s, 2s delays
# - Network timeouts → 0.5s, 1s, 2s delays
# - Does NOT retry 4xx client errors (except 429)DataService uses a single _fetch_with_cache() method:
async def _fetch_with_cache(self, cache_type, cache_key, fetch_fn, error_prefix):
# 1. Check for fresh cached data
# 2. Fall back to stale data if in background mode
# 3. Fetch from API with retry
# 4. Fall back to stale data on error
# Usage:
async def get_stock_data(self, ticker: str) -> dict:
return await self._fetch_with_cache(
cache_type="polygon",
cache_key=ticker,
fetch_fn=lambda: self.polygon.get_stock_data(ticker),
error_prefix="POLYGON"
)Result: Eliminated 90+ lines of duplicate code across methods.
Two retrieval methods with different TTL enforcement:
get_fresh()- Returns data only if age <CACHE_TTL_MINUTES(10 minutes default)get_stale()- Returns data regardless of age (used when API calls fail)
graph TB
User[User Browser]
subgraph "Streamlit Application"
UI[UI Layer<br/>main.py, components.py]
subgraph "Services Layer"
DataService[Data Service<br/>data_service.py]
Cache[Cache Service<br/>cache.py]
end
Retry[Retry Decorator<br/>retry.py]
subgraph "API Clients"
PolygonClient[Polygon Client<br/>polygon.py]
NewsClient[NewsDataHub Client<br/>newsdatahub.py]
OpenAIClient[OpenAI Client<br/>openai_client.py]
end
end
subgraph "Cache Storage"
CacheFiles[(JSON Cache Files)]
end
subgraph "External APIs"
PolygonAPI[Polygon.io API<br/>Stock Data]
NewsAPI[NewsDataHub API<br/>News Articles]
OpenAI[OpenAI API<br/>AI Insights]
end
User --> UI
UI --> DataService
DataService --> Cache
DataService --> PolygonClient
DataService --> NewsClient
DataService --> OpenAIClient
Cache <--> CacheFiles
PolygonClient --> Retry
PolygonClient --> PolygonAPI
NewsClient --> Retry
NewsClient --> NewsAPI
OpenAIClient --> Retry
OpenAIClient --> OpenAI
style UI fill:#22c55e,stroke:#fff,color:#000
style DataService fill:#3b82f6,stroke:#fff,color:#fff
style Cache fill:#8b5cf6,stroke:#fff,color:#fff
style CacheFiles fill:#f59e0b,stroke:#fff,color:#000
- Python 3.9+
- API Keys (Polygon and NewsDataHub have free tiers):
- Polygon.io (now Massive.com) - Stock data
- NewsDataHub - News
- OpenAI - AI insights
git clone https://github.com/your-username/multi-api-financial-dashboard.git
cd multi-api-financial-dashboard
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txtcp .env.example .envEdit .env with your API keys:
POLYGON_API_KEY=your_polygon_api_key_here
NEWSDATAHUB_API_KEY=your_newsdatahub_api_key_here
OPENAI_API_KEY=your_openai_api_key_here
# Optional settings
CACHE_TTL_MINUTES=10
LOG_LEVEL=INFOstreamlit run app/main.pyOpens at http://localhost:8501
- Select a stock (NFLX, GOOGL, or TSLA)
- View real-time price with percentage change
- Explore 1-month price chart
- See related competitor stocks
- Read latest news (5 articles)
- Generate AI insights (button click)
First load fetches fresh data. Subsequent requests within TTL (10 minutes) serve from cache. Cache age indicators show when viewing cached data:
⏱️ Price data from 5m ago
Purpose: Prevent unpredictable API costs in production.
Local Mode (BACKGROUND_REFRESH=false, default):
- User requests may trigger API calls when cache is stale
- API quota scales with user traffic
Background Refresh Mode (BACKGROUND_REFRESH=true):
- User requests never trigger API calls
- Separate cron job (
scripts/refresh_cache.py) refreshes cache on fixed schedule - API quota is constant regardless of traffic
Set in .env:
BACKGROUND_REFRESH=trueRun refresh job via cron (every 3 hours):
0 */3 * * * /path/to/venv/bin/python /path/to/scripts/refresh_cache.pyTradeoff: Data can be up to 3 hours stale, but API costs are predictable.
# Run all tests
pytest
# With coverage
pytest --cov=app --cov-report=html --cov-report=term-missing
# Specific tests
pytest tests/test_cache.py
pytest tests/test_polygon.pyCoverage: ~25-30% focusing on configuration, cache operations, API transformations, and news deduplication.
multi-api-financial-dashboard/
├── app/
│ ├── main.py # Streamlit entry point
│ ├── config.py # Configuration with validation
│ ├── api/ # API clients with @retry_with_backoff
│ ├── services/ # Cache and data orchestration
│ ├── ui/components.py # UI components
│ └── utils/ # Logging and retry decorator
├── scripts/refresh_cache.py # Background refresh job
├── tests/ # Test suite
├── cache/ # JSON cache files (git-ignored)
├── .env.example # Environment template
└── requirements.txt # Dependencies
Configure via environment variables (.env):
| Variable | Default | Description |
|---|---|---|
BACKGROUND_REFRESH |
false |
If true, app only reads cache |
CACHE_TTL_MINUTES |
10 |
Cache freshness duration |
CACHE_MAX_AGE_HOURS |
24 |
Delete cache older than this |
POLYGON_API_KEY |
— | Required |
NEWSDATAHUB_API_KEY |
— | Required |
OPENAI_API_KEY |
— | Required |
LOG_LEVEL |
INFO |
Logging verbosity |
- Verify all API keys in
.env - Check Python version (3.9+)
- Reinstall:
pip install -r requirements.txt
- Check
logs/app.logfor errors - Verify API keys are valid with quota remaining
- Test network connectivity
- Ensure pytest installed
- Check file permissions
- Run with verbose:
pytest -v
MIT License - see LICENSE file for details.
