Complete Guide: Integrating EODHD API with Django for Real-Time Stock Market Data
Introduction
In today's data-driven financial landscape, accessing accurate and real-time stock market information is crucial for building robust financial applications. Whether you're developing an investment platform, portfolio tracker, or financial analysis tool, integrating a reliable market data API is essential.
EODHD (End of Day Historical Data) is a comprehensive financial data provider offering access to stock fundamentals, historical prices, real-time quotes, and extensive market coverage across global exchanges. In this comprehensive guide, we'll walk through the complete process of integrating EODHD API with a Django web application to fetch and display stock market data professionally.
By the end of this tutorial, you'll have a fully functional Django application that can retrieve company fundamentals, financial metrics, and display stock information dynamically on your website.
Prerequisites
Before we begin, ensure you have the following:
- Python 3.8 or higher installed
- Basic understanding of Django framework
- EODHD API key (sign up at eodhistoricaldata.com)
- Familiarity with REST APIs and HTTP requests
- A code editor (VS Code, PyCharm, etc.)
Part 1: Project Setup and Configuration
Step 1: Install Required Dependencies
First, create a new Django project or use an existing one. Install the necessary Python packages:
pip install django requests python-decouple
Package breakdown:
django: The web frameworkrequests: For making HTTP API callspython-decouple: For secure environment variable management
Step 2: Configure Environment Variables
Create a .env file in your project root to store sensitive credentials securely:
EODHD_API_KEY=your_api_key_here
DEBUG=True
SECRET_KEY=your_django_secret_key
Security Best Practice: Never commit your .env file to version control. Add it to .gitignore immediately.
Step 3: Create Django App Structure
Generate a new Django app for handling market data:
python manage.py startapp market_data
Add the app to your INSTALLED_APPS in settings.py:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'market_data', # Your new app
]
Part 2: Building the EODHD Service Layer
Step 1: Create the API Service Class
Create a file market_data/services.py to handle all EODHD API interactions:
import requests
from django.conf import settings
from decouple import config
import logging
logger = logging.getLogger(__name__)
class EODHDService:
"""
Service class for interacting with EODHD API
Handles all API calls and data extraction
"""
BASE_URL = "https://eodhistoricaldata.com/api"
API_KEY = config('EODHD_API_KEY', default='')
@classmethod
def get_fundamentals(cls, symbol, exchange="US"):
"""
Fetch fundamental data for a given stock symbol
Args:
symbol (str): Stock ticker symbol (e.g., 'AAPL')
exchange (str): Exchange code (default: 'US')
Returns:
dict: Fundamental data or None if error
"""
url = f"{cls.BASE_URL}/fundamentals/{symbol}.{exchange}"
params = {
"api_token": cls.API_KEY,
"fmt": "json",
}
try:
response = requests.get(url, params=params, timeout=10)
response.raise_for_status()
data = response.json()
if not data:
logger.warning(f"Empty response for {symbol}")
return None
return data
except requests.exceptions.RequestException as e:
logger.error(f"Error fetching fundamentals for {symbol}: {str(e)}")
return None
@classmethod
def search_symbols(cls, query):
"""
Search for stock symbols by company name or ticker
Args:
query (str): Search query
Returns:
list: List of matching symbols
"""
url = f"{cls.BASE_URL}/search/{query}"
params = {
"api_token": cls.API_KEY,
"fmt": "json",
}
try:
response = requests.get(url, params=params, timeout=10)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
logger.error(f"Error searching symbols: {str(e)}")
return []
@classmethod
def extract_company_details(cls, fundamentals):
"""
Extract relevant company information from fundamentals data
Args:
fundamentals (dict): Raw API response
Returns:
dict: Cleaned company details
"""
try:
details = {}
if 'General' in fundamentals:
general = fundamentals['General']
details['name'] = general.get('Name', '')
details['description'] = general.get('Description', '')
details['sector'] = general.get('Sector', '')
details['industry'] = general.get('Industry', '')
details['country'] = general.get('Country', '')
details['exchange'] = general.get('Exchange', '')
details['currency'] = general.get('CurrencyCode', '')
details['website'] = general.get('WebURL', '')
details['employees'] = general.get('FullTimeEmployees', '')
return details
except Exception as e:
logger.error(f"Error extracting company details: {str(e)}")
return {}
@classmethod
def extract_financial_metrics(cls, fundamentals):
"""
Extract key financial metrics from fundamentals data
Args:
fundamentals (dict): Raw API response
Returns:
dict: Financial metrics
"""
try:
metrics = {}
# Market capitalization
metrics['market_cap'] = fundamentals.get('Highlights', {}).get('MarketCapitalization', 0)
# Extract debt from balance sheet
balance_sheet = fundamentals.get('Financials', {}).get('Balance_Sheet', {})
yearly_bs = balance_sheet.get('yearly', {})
if yearly_bs:
latest_year = sorted(yearly_bs.keys(), reverse=True)[0]
latest_data = yearly_bs[latest_year]
# Total debt
metrics['total_debt'] = float(latest_data.get('totalDebt', 0))
else:
metrics['total_debt'] = 0
# Extract revenue from income statement
income_statement = fundamentals.get('Financials', {}).get('Income_Statement', {})
yearly_is = income_statement.get('yearly', {})
if yearly_is:
latest_year = sorted(yearly_is.keys(), reverse=True)[0]
latest_data = yearly_is[latest_year]
metrics['total_revenue'] = float(latest_data.get('totalRevenue', 0))
else:
metrics['total_revenue'] = 0
return metrics
except Exception as e:
logger.error(f"Error extracting financial metrics: {str(e)}")
return {
'market_cap': 0,
'total_debt': 0,
'total_revenue': 0
}
Part 3: Creating Django Models
Define models to store stock data in market_data/models.py:
from django.db import models
class Company(models.Model):
"""
Model to store company information and financial data
"""
code = models.CharField(max_length=10, primary_key=True, verbose_name="Stock Symbol")
name = models.CharField(max_length=255, verbose_name="Company Name")
description = models.TextField(blank=True, verbose_name="Description")
sector = models.CharField(max_length=100, blank=True, verbose_name="Sector")
industry = models.CharField(max_length=100, blank=True, verbose_name="Industry")
country = models.CharField(max_length=100, blank=True, verbose_name="Country")
exchange = models.CharField(max_length=20, blank=True, verbose_name="Exchange")
website = models.URLField(blank=True, verbose_name="Website")
currency = models.CharField(max_length=10, blank=True, verbose_name="Currency")
# Financial metrics
market_cap = models.FloatField(null=True, blank=True, verbose_name="Market Cap")
total_debt = models.FloatField(null=True, blank=True, verbose_name="Total Debt")
total_revenue = models.FloatField(null=True, blank=True, verbose_name="Total Revenue")
# Metadata
last_updated = models.DateTimeField(auto_now=True, verbose_name="Last Updated")
class Meta:
verbose_name = "Company"
verbose_name_plural = "Companies"
ordering = ['name']
def __str__(self):
return f"{self.code} - {self.name}"
@property
def debt_to_market_cap_ratio(self):
"""Calculate debt to market cap ratio"""
if self.market_cap and self.market_cap > 0 and self.total_debt:
return (self.total_debt / self.market_cap) * 100
return None
Run migrations to create the database tables:
python manage.py makemigrations
python manage.py migrate
Part 4: Building Views and URL Routing
Step 1: Create Views
In market_data/views.py, create views to handle stock data requests:
from django.shortcuts import render
from django.http import JsonResponse
from .models import Company
from .services import EODHDService
import logging
logger = logging.getLogger(__name__)
def company_detail(request, symbol):
"""
Display detailed information for a specific stock symbol
"""
try:
# Check if data exists in database
company_obj = Company.objects.filter(code=symbol).first()
# Determine if we need to fetch fresh data
needs_refresh = not company_obj or request.GET.get('refresh') == 'true'
if needs_refresh:
# Fetch from EODHD API
fundamentals = EODHDService.get_fundamentals(symbol, exchange="US")
if not fundamentals:
return render(request, 'market_data/error.html', {
'error': f'Could not find data for {symbol}'
})
# Extract data
details = EODHDService.extract_company_details(fundamentals)
metrics = EODHDService.extract_financial_metrics(fundamentals)
# Save or update in database
company = company_obj or Company(code=symbol)
company.name = details.get('name', symbol)
company.description = details.get('description', '')
company.sector = details.get('sector', '')
company.industry = details.get('industry', '')
company.country = details.get('country', '')
company.exchange = details.get('exchange', '')
company.website = details.get('website', '')
company.currency = details.get('currency', '')
company.market_cap = metrics.get('market_cap')
company.total_debt = metrics.get('total_debt')
company.total_revenue = metrics.get('total_revenue')
company.save()
else:
company = company_obj
# Calculate financial ratios
debt_ratio = company.debt_to_market_cap_ratio or 0
context = {
'company': company,
'debt_ratio': debt_ratio,
}
return render(request, 'market_data/company_detail.html', context)
except Exception as e:
logger.error(f"Error in company_detail: {str(e)}")
return render(request, 'market_data/error.html', {
'error': f'An error occurred: {str(e)}'
})
def search_companies(request):
"""
AJAX endpoint for searching companies
"""
query = request.GET.get('q', '')
if len(query) < 2:
return JsonResponse({'results': []})
results = EODHDService.search_symbols(query)
formatted_results = []
for result in results:
formatted_results.append({
'code': result.get('Code'),
'name': result.get('Name'),
'exchange': result.get('Exchange'),
'country': result.get('Country'),
})
return JsonResponse({'results': formatted_results})
Step 2: Configure URLs
Create market_data/urls.py:
from django.urls import path
from . import views
app_name = 'market_data'
urlpatterns = [
path('company/<str:symbol>/', views.company_detail, name='company_detail'),
path('search/', views.search_companies, name='search'),
]
Include in your main urls.py:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('market/', include('market_data.urls')),
]
Part 5: Creating Templates
Create a template at market_data/templates/market_data/company_detail.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ company.name }} ({{ company.code }}) - Stock Information</title>
<style>
body { font-family: Arial, sans-serif; max-width: 1200px; margin: 0 auto; padding: 20px; }
.company-header { border-bottom: 2px solid #333; padding-bottom: 20px; margin-bottom: 20px; }
.metrics-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; }
.metric-card { background: #f5f5f5; padding: 20px; border-radius: 8px; }
.metric-label { font-size: 14px; color: #666; margin-bottom: 5px; }
.metric-value { font-size: 24px; font-weight: bold; color: #333; }
</style>
</head>
<body>
<div class="company-header">
<h1>{{ company.name }}</h1>
<p><strong>Symbol:</strong> {{ company.code }} | <strong>Exchange:</strong> {{ company.exchange }}</p>
<p><strong>Sector:</strong> {{ company.sector }} | <strong>Industry:</strong> {{ company.industry }}</p>
</div>
<div class="metrics-grid">
<div class="metric-card">
<div class="metric-label">Market Capitalization</div>
<div class="metric-value">${{ company.market_cap|floatformat:0|default:"N/A" }}</div>
</div>
<div class="metric-card">
<div class="metric-label">Total Revenue</div>
<div class="metric-value">${{ company.total_revenue|floatformat:0|default:"N/A" }}</div>
</div>
<div class="metric-card">
<div class="metric-label">Total Debt</div>
<div class="metric-value">${{ company.total_debt|floatformat:0|default:"N/A" }}</div>
</div>
<div class="metric-card">
<div class="metric-label">Debt to Market Cap Ratio</div>
<div class="metric-value">{{ debt_ratio|floatformat:2 }}%</div>
</div>
</div>
<div style="margin-top: 30px;">
<h2>Company Description</h2>
<p>{{ company.description|default:"No description available." }}</p>
</div>
<div style="margin-top: 20px;">
<p><small>Last updated: {{ company.last_updated }}</small></p>
<a href="?refresh=true">Refresh Data</a>
</div>
</body>
</html>
Part 6: Best Practices and Optimization
1. Implement Caching
Add caching to reduce API calls:
from django.core.cache import cache
def get_company_data(symbol):
cache_key = f'company_{symbol}'
cached_data = cache.get(cache_key)
if cached_data:
return cached_data
# Fetch from API
data = EODHDService.get_fundamentals(symbol)
cache.set(cache_key, data, timeout=3600) # Cache for 1 hour
return data
2. Error Handling
Always implement robust error handling:
try:
data = EODHDService.get_fundamentals(symbol)
except requests.exceptions.Timeout:
logger.error("API request timed out")
except requests.exceptions.RequestException as e:
logger.error(f"API request failed: {str(e)}")
3. Rate Limiting
Respect API rate limits by implementing throttling:
import time
from functools import wraps
def rate_limit(calls_per_minute=60):
def decorator(func):
last_called = [0.0]
@wraps(func)
def wrapper(*args, **kwargs):
elapsed = time.time() - last_called[0]
wait_time = 60.0 / calls_per_minute
if elapsed < wait_time:
time.sleep(wait_time - elapsed)
result = func(*args, **kwargs)
last_called[0] = time.time()
return result
return wrapper
return decorator
4. Logging
Implement comprehensive logging:
import logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('market_data.log'),
logging.StreamHandler()
]
)
Part 7: Testing Your Integration
Create tests in market_data/tests.py:
from django.test import TestCase
from .services import EODHDService
class EODHDServiceTests(TestCase):
def test_get_fundamentals(self):
"""Test fetching fundamental data"""
data = EODHDService.get_fundamentals('AAPL')
self.assertIsNotNone(data)
self.assertIn('General', data)
def test_search_symbols(self):
"""Test symbol search"""
results = EODHDService.search_symbols('Apple')
self.assertIsInstance(results, list)
self.assertGreater(len(results), 0)
Run tests:
python manage.py test market_data
Conclusion
You've successfully integrated EODHD API with your Django application! This implementation provides a solid foundation for building sophisticated financial applications with real-time market data.
Key Takeaways:
- Secure API key management using environment variables
- Service layer pattern for clean API interactions
- Database caching for improved performance
- Robust error handling and logging
- Professional template structure
Next Steps:
- Add real-time price updates using WebSockets
- Implement user watchlists and portfolios
- Create data visualization with charts
- Add historical price data analysis
- Implement advanced filtering and screening
This integration can be extended to support multiple exchanges, cryptocurrency data, forex rates, and much more. The EODHD API offers extensive coverage, making it an excellent choice for any financial data application.
Additional Resources
_________________
By Azzani
Discussion 0
No comments yet. Be the first to start the discussion!
Leave a Comment