Django February 04, 2026 8 min read

How to Integrate EODHD API with Django: Complete Guide for Stock Market Data Display

Learn how to integrate EODHD Historical Data API with your Django project to fetch and display real-time stock market information. Step-by-step tutorial with code examples, best practices, and production-ready implementation.

A
azzani
3 views
How to Integrate EODHD API with Django: Complete Guide for Stock Market Data Display

Table of Contents

  • Loading table of contents...

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 framework
  • requests: For making HTTP API calls
  • python-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

EODHD API Documentation

Django Official Documentation

REST API Best Practices

 

_________________

By Azzani

Related Articles

Discussion 0

No comments yet. Be the first to start the discussion!

Leave a Comment