Please note, API data availability depends on your subscription plan. Some data isn’t included in the free plan. Visit our pricing page to find the plan that fits your needs.
This article builds upon the previous three in the series, “Build a Financial Trading Dashboard with Python Django”, “Enhancing the Financial Trading Dashboard with Python Django”, and “AnyChart integration for the Financial Trading Dashboard with Python Django“.
The EODHD APIs include an endpoint that provides Fundamental Data, which is documented here. While it offers a substantial amount of data, I am primarily interested in extracting five specific sections for the Django application.
{
"General": {
"Description": "Apple Inc. designs, manufactures, and markets smartphones, personal computers, tablets, wearables, and accessories worldwide. The company offers iPhone, a line of smartphones; Mac, a line of personal computers; iPad, a line of multi-purpose tablets; and wearables, home, and accessories comprising AirPods, Apple TV, Apple Watch, Beats products, and HomePod. It also provides AppleCare support and cloud services; and operates various platforms, including the App Store that allow customers to discover and download applications and digital content, such as books, music, video, games, and podcasts, as well as advertising services include third-party licensing arrangements and its own advertising platforms. In addition, the company offers various subscription-based services, such as Apple Arcade, a game subscription service; Apple Fitness+, a personalized fitness service; Apple Music, which offers users a curated listening experience with on-demand radio stations; Apple News+, a subscription news and magazine service; Apple TV+, which offers exclusive original content; Apple Card, a co-branded credit card; and Apple Pay, a cashless payment service, as well as licenses its intellectual property. The company serves consumers, and small and mid-sized businesses; and the education, enterprise, and government markets. It distributes third-party applications for its products through the App Store. The company also sells its products through its retail and online stores, and direct sales force; and third-party cellular network carriers, wholesalers, retailers, and resellers. Apple Inc. was founded in 1976 and is headquartered in Cupertino, California.",
"Address": "One Apple Park Way, Cupertino, CA, United States, 95014",
},
"Highlights": {
"MarketCapitalization": 3553119698944,
"MarketCapitalizationMln": 3553119.6989,
"EBITDA": 134660997120,
"PERatio": 38.6612,
"PEGRatio": 2.4109,
"WallStreetTargetPrice": 244.4774,
"BookValue": 3.767,
"DividendShare": 0.98,
"DividendYield": 0.0043,
"EarningsShare": 6.08,
"EPSEstimateCurrentYear": 6.7,
"EPSEstimateNextYear": 7.46,
"EPSEstimateNextQuarter": 2.38,
"EPSEstimateCurrentQuarter": 1.6,
"MostRecentQuarter": "2024-09-30",
"ProfitMargin": 0.2397,
"OperatingMarginTTM": 0.3117,
"ReturnOnAssetsTTM": 0.2146,
"ReturnOnEquityTTM": 1.5741,
"RevenueTTM": 391034994688,
"RevenuePerShareTTM": 25.485,
"QuarterlyRevenueGrowthYOY": 0.061,
"GrossProfitTTM": 170782000000,
"DilutedEpsTTM": 6.08,
"QuarterlyEarningsGrowthYOY": -0.341
},
"SharesStats": {
"SharesOutstanding": 15115799552,
"SharesFloat": 15091184209,
"PercentInsiders": 2.056,
"PercentInstitutions": 61.915,
"SharesShort": null,
"SharesShortPriorMonth": null,
"ShortRatio": null,
"ShortPercentOutstanding": null,
"ShortPercentFloat": 0.0088
},
"Earnings": {
"History": {
"2025-06-30": {
"reportDate": "2025-07-30",
"date": "2025-06-30",
"beforeAfterMarket": "BeforeMarket",
"currency": "USD",
"epsActual": null,
"epsEstimate": null,
"epsDifference": null,
"surprisePercent": null
},
<snip>
},
"Financials": {
"Cash_Flow": {
"currency_symbol": "USD",
"quarterly": {
"2024-09-30": {
"date": "2024-09-30",
"filing_date": "2024-11-01",
"currency_symbol": "USD",
"investments": "1445000000.00",
"changeToLiabilities": null,
"totalCashflowsFromInvestingActivities": null,
"netBorrowings": null,
"totalCashFromFinancingActivities": "-24948000000.00",
"changeToOperatingActivities": null,
"netIncome": "14736000000.00",
"changeInCash": "3308000000.00",
"beginPeriodCashFlow": "26635000000.00",
"endPeriodCashFlow": "29943000000.00",
"totalCashFromOperatingActivities": "26811000000.00",
"issuanceOfCapitalStock": null,
"depreciation": "2911000000.00",
"otherCashflowsFromInvestingActivities": null,
"dividendsPaid": "3804000000.00",
"changeToInventory": "-1087000000.00",
"changeToAccountReceivables": "-22941000000.00",
"salePurchaseOfStock": "-25083000000.00",
"otherCashflowsFromFinancingActivities": "-448000000.00",
"changeToNetincome": null,
"capitalExpenditures": "2908000000",
"changeReceivables": null,
"cashFlowsOtherOperating": null,
"exchangeRateChanges": null,
"cashAndCashEquivalentsChanges": null,
"changeInWorkingCapital": "6608000000.00",
"stockBasedCompensation": "2858000000.00",
"otherNonCashItems": "-302000000.00",
"freeCashFlow": "23903000000.00"
},
<snip>
},
}
This is just a small subset of the data available. In addition to this, I also want to retrieve the Open, High, Close, Previous Close, and Volume. I obtained this data from the EOD endpoint, as documented here.
And finally, I want to retrieve the market capitalisation for the market as well. This needs to be retrieved by another API endpoint which is documented here.
The data when graphed, will look like this…
I currently have a view that provides data for the AnyChart stock chart and the Bootstrap data table. My intention was to include this fundamental and market cap data on the same page by incorporating it into the existing view. However, I wanted to avoid chaining four API calls sequentially, as that would result in slower performance. Instead, I utilised the asyncio
library and converted the view into an asynchronous function. This allowed me to dispatch the API requests asynchronously (all at the same time). Once all the data was retrieved, I processed it as required and returned it to the template as usual.
Quick jump:
Updating the View – fetch_historical_data
The first step is we need to import “asyncio” and “httpx” in our “views.py”.
import json
import asyncio
import httpx
from django.conf import settings
from datetime import datetime, timedelta
import requests
from django.shortcuts import render, get_object_or_404, redirect
from .models import SPGlobalIndex, IndexConstituent, HistoricalMarketData, SearchData
The updated “fetch_historical_data” in “views.py” now looks like this…
async def fetch_historical_data(request, market, interval):
now = datetime.now()
if interval in ["m", "w", "d"]:
end_date = now.date().isoformat()
start_date = (now - timedelta(days=300)).date().isoformat()
else:
end_date = now.strftime("%Y-%m-%dT%H:%M")
start_date = (now - timedelta(hours=300)).strftime("%Y-%m-%dT%H:%M")
start_date = request.GET.get("from", start_date)
end_date = request.GET.get("to", end_date)
def parse_datetime(dt_str):
try:
return datetime.strptime(dt_str, "%Y-%m-%dT%H:%M:%S")
except ValueError:
try:
return datetime.strptime(dt_str, "%Y-%m-%dT%H:%M")
except ValueError:
return datetime.strptime(dt_str, "%Y-%m-%d")
start_date_parsed = parse_datetime(start_date)
end_date_parsed = parse_datetime(end_date)
if interval in ["m", "w", "d"]:
start_date = start_date_parsed.strftime("%Y-%m-%d")
end_date = end_date_parsed.strftime("%Y-%m-%d")
else:
start_date_unix = int(start_date_parsed.timestamp())
end_date_unix = int(end_date_parsed.timestamp())
endpoint = "eod" if interval in ["m", "w", "d"] else "intraday"
interval_type = "period" if interval in ["m", "w", "d"] else "interval"
historical_url = (
f"https://eodhd.com/api/{endpoint}/{market}?"
f"{interval_type}={interval}&from={start_date if interval in ['m', 'w', 'd'] else start_date_unix}"
f"&to={end_date if interval in ['m', 'w', 'd'] else end_date_unix}"
f"&api_token={settings.EODHD_API_TOKEN}&fmt=json"
)
fundamental_url = f"https://eodhd.com/api/fundamentals/{market}?api_token={settings.EODHD_API_TOKEN}&fmt=json"
today_minus_7_days = (now - timedelta(days=7)).strftime("%Y-%m-%d")
ohlc_url = (
f"https://eodhd.com/api/eod/{market}?"
f"period=d&from={today_minus_7_days}"
f"&api_token={settings.EODHD_API_TOKEN}&fmt=json"
)
market_cap_url = f"https://eodhd.com/api/historical-market-cap/{market}?api_token={settings.EODHD_API_TOKEN}&fmt=json"
async def fetch_data(url):
async with httpx.AsyncClient() as client:
response = await client.get(url)
return response.json()
historical_data, fundamental_data, ohlc_data, market_cap_data = await asyncio.gather(
fetch_data(historical_url),
fetch_data(fundamental_url),
fetch_data(ohlc_url),
fetch_data(market_cap_url),
)
# Process historical data
def format_unix_timestamp(unix_ts):
return datetime.utcfromtimestamp(unix_ts).strftime("%Y-%m-%d %H:%M:%S")
for entry in historical_data:
if "date" in entry:
entry["timestamp"] = entry.pop("date")
elif "datetime" in entry:
datetime_value = entry.pop("datetime")
try:
entry["timestamp"] = format_unix_timestamp(int(datetime_value))
except ValueError:
entry["timestamp"] = datetime_value
if not historical_data or "error" in historical_data:
historical_data = []
raw_data = historical_data
historical_data_json = json.dumps(historical_data)
# Process OHLC data
ohlc_latest = {}
if ohlc_data and len(ohlc_data) >= 2:
latest_entry = ohlc_data[-1]
second_last_entry = ohlc_data[-2]
ohlc_latest = {
"Open": latest_entry.get("open", "-"),
"High": latest_entry.get("high", "-"),
"Low": latest_entry.get("low", "-"),
"Close": latest_entry.get("close", "-"),
"Volume": latest_entry.get("volume", "-"),
"Prev_Close": second_last_entry.get("close", "-"),
}
# Process fundamental data
name = fundamental_data.get("General", {}).get("Name", "Unknown Company")
description = fundamental_data.get("General", {}).get("Description", "-")
highlights = fundamental_data.get("Highlights", {})
technicals = fundamental_data.get("Technicals", {})
shares_stats = fundamental_data.get("SharesStats", {})
financials = fundamental_data.get("Financials", {})
cash_flow = financials.get("Cash_Flow", {}) # dividendsPaid
cash_flow_quarterly = cash_flow.get("quarterly", {})
dividends_paid = [
{"date": value.get("date"), "value": value.get("dividendsPaid", 0) or 0}
for value in cash_flow_quarterly.values()
]
dividends_paid = sorted(dividends_paid, key=lambda x: x["date"])
dividends_paid_json = json.dumps(dividends_paid)
earnings = fundamental_data.get("Earnings", {})
earnings_history = earnings.get("History", {}) # epsActual
eps_actual = [
{"date": value.get("reportDate"), "value": value.get("epsActual", 0) or 0}
for value in earnings_history.values()
]
eps_actual = sorted(eps_actual, key=lambda x: x["date"])
eps_actual_json = json.dumps(eps_actual)
table_data = {
"Prev_Close": "-",
"Volume": "-",
"Low": "-",
"Market_Cap": highlights.get("MarketCapitalization", "-"),
"Shares_Outstanding": shares_stats.get("SharesOutstanding", "-"),
"EPS": highlights.get("EarningsShare", "-"),
"Beta": technicals.get("Beta", "-"),
"Open": "-",
"High": "-",
"52_wk_Range": f"{technicals.get('52WeekLow', '-')} - {technicals.get('52WeekHigh', '-')}",
"PE_Ratio": highlights.get("PERatio", "-"),
"Revenue": highlights.get("RevenueTTM", "-"),
"Dividends_Yield": highlights.get("DividendYield", "-"),
}
market_cap = [
{"date": entry["date"], "value": entry["value"]}
for entry in market_cap_data.values()
]
market_cap_json = json.dumps(market_cap, indent=4)
return render(
request,
"historical/historical_data.html",
{
"market": market,
"interval": interval,
"historical_data": raw_data, # Raw Python data for the table
"historical_data_json": historical_data_json, # JSON for the script
"fundamental_name": name,
"fundamental_description": description,
"fundamental_table": table_data,
"dividends_paid_json": dividends_paid_json,
"eps_actual_json": eps_actual_json,
"ohlc_latest": ohlc_latest,
"market_cap_json": market_cap_json,
"start_date": (
start_date
if interval in ["m", "w", "d"]
else start_date_parsed.strftime("%Y-%m-%dT%H:%M")
),
"end_date": (
end_date
if interval in ["m", "w", "d"]
else end_date_parsed.strftime("%Y-%m-%dT%H:%M")
),
},
)
A few points to pay attention to here:
- The function “def” is now “async def”.
- The endpoint to retrieve the historical data is, “historical_url”, the endpoint to retrieve the fundamental data is, “fundamental_url”, the endpoint to retrieve the OHLC data is, “ohlc_url” and the endpoint to retrieve the market cap data is “market_cap_url”.
- You may wonder what I’m doing with “today_minus_7_days”. The EOD API by default returns almost all the data which is a lot more than we need. The API only supports a date range for filtering and I only need the last two days. To be on the safe side, I retrieved the last 7 days and will only use the last 2.
- The return has now been updated to include all the information we’ll need to be passed to our template, “historical_data.html”.
What I like about Django is how structured it is. Once you have your application structure setup, it is quick to modify and expand on.
Updating the Template – historical_data.html
This template will now have three sections. The fundamental data, the AnyChart stock chart, and the Bootstrap data table. The updated like now looks like this…
<!DOCTYPE html>
<html lang="en">
<head>
<title>Historical Data for {{ market }} ({{ interval }})</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdn.datatables.net/1.10.21/css/dataTables.bootstrap4.min.css">
<link rel="stylesheet" href="https://cdn.datatables.net/buttons/1.7.1/css/buttons.bootstrap4.min.css">
<style>
body {
background-color: #343a40;
color: #ffffff;
}
.table {
background-color: #212529;
}
.table th,
.table td {
color: #ffffff;
}
.chart-container {
margin-bottom: 20px;
}
.dt-buttons .btn {
margin-right: 10px;
}
.page-item.active .page-link {
z-index: 3;
color: #ffffff !important;
background-color: #495057 !important;
border-color: #495057 !important;
}
.page-link {
color: #ffffff !important;
background-color: #6c757d !important;
border-color: #343a40 !important;
}
.page-link:hover {
color: #adb5bd !important;
background-color: #5a6268 !important;
border-color: #343a40 !important;
}
.dataTables_wrapper .dataTables_paginate .paginate_button {
color: #ffffff !important;
background-color: #6c757d !important;
border: 1px solid #343a40 !important;
}
.dataTables_wrapper .dataTables_paginate .paginate_button:hover {
background-color: #5a6268 !important;
border: 1px solid #343a40 !important;
}
.dataTables_wrapper .dataTables_paginate .paginate_button.current {
color: #ffffff !important;
background-color: #495057 !important;
border: 1px solid #343a40 !important;
}
.dataTables_wrapper .dataTables_paginate .paginate_button.disabled,
.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover {
background-color: #6c757d !important;
color: #ffffff !important;
}
.btn-dark {
background-color: #6c757d !important;
border-color: #6c757d !important;
color: #ffffff !important;
}
.btn-dark:hover {
background-color: #5a6268 !important;
border-color: #5a6268 !important;
}
</style>
</head>
<body>
<div class="container mt-3">
<!-- Fundamental Data -->
<div class="card mb-4">
<div class="card-header">
<h4 class="text-dark">{{ fundamental_name }} Financial Data Overview</h4>
</div>
<div class="card-body">
<p class="text-dark">{{ fundamental_description }}</p>
<table class="table table-bordered">
<thead>
<tr>
<th style="width: 25%;">Metric</th>
<th style="width: 25%;">Value</th>
<th style="width: 25%;">Metric</th>
<th style="width: 25%;">Value</th>
</tr>
</thead>
<tbody>
<tr>
<td>Open</td>
<td>{{ ohlc_latest.Open }}</td>
<td>Market Cap</td>
<td>{{ fundamental_table.Market_Cap }}</td>
</tr>
<tr>
<td>High</td>
<td>{{ ohlc_latest.High }}</td>
<td>Dividends (Yield)</td>
<td>{{ fundamental_table.Dividends_Yield }}</td>
</tr>
<tr>
<td>Low</td>
<td>{{ ohlc_latest.Low }}</td>
<td>Revenue</td>
<td>{{ fundamental_table.Revenue }}</td>
</tr>
<tr>
<td>52 wk Range</td>
<td>{{ fundamental_table.52_wk_Range }}</td>
<td>P/E Ratio</td>
<td>{{ fundamental_table.PE_Ratio }}</td>
</tr>
<tr>
<td>Prev. Close</td>
<td>{{ ohlc_latest.Prev_Close }}</td>
<td>Shares Outstanding</td>
<td>{{ fundamental_table.Shares_Outstanding }}</td>
</tr>
<tr>
<td>Close</td>
<td>{{ ohlc_latest.Close }}</td>
<td>EPS</td>
<td>{{ fundamental_table.EPS }}</td>
</tr>
<tr>
<td>Volume</td>
<td>{{ ohlc_latest.Volume }}</td>
<td>Beta</td>
<td>{{ fundamental_table.Beta }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Historical Data - AnyChart and Bootstrap -->
<div id="historical-data">
<div class="container mt-5">
<h2 class="mb-4">Historical Data for {{ market }} ({{ interval }})</h2>
<div class="row">
<div class="col-12 chart-container">
<div id="candlestickChart" style="height: 500px; width: 100%;"></div>
</div>
</div>
<div class="row">
<div class="col-12 chart-container">
<div id="dividendsChart" style="height: 500px; width: 100%;"></div>
</div>
</div>
<div class="row">
<div class="col-12 chart-container">
<div id="epsChart" style="height: 500px; width: 100%;"></div>
</div>
</div>
<div class="row">
<div class="col-12 chart-container">
<div id="marketCapChart" style="height: 500px; width: 100%;"></div>
</div>
</div>
<div class="row">
<div class="col-12">
<table id="historicalTable" class="table table-dark table-striped table-bordered">
<thead class="thead-dark">
<tr>
<th>Timestamp</th>
<th>Open</th>
<th>High</th>
<th>Low</th>
<th>Close</th>
<th>Volume</th>
</tr>
</thead>
<tbody>
{% for entry in historical_data %}
<tr>
<td>{{ entry.timestamp }}</td>
<td>{{ entry.open }}</td>
<td>{{ entry.high }}</td>
<td>{{ entry.low }}</td>
<td>{{ entry.close }}</td>
<td>{{ entry.volume }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<a href="javascript:history.back()" class="btn btn-dark mt-4">Back</a>
</div>
</div>
</div>
<script id="historicalData" type="application/json">
{{ historical_data_json|safe }}
</script>
<script id="dividendsPaid" type="application/json">
{{ dividends_paid_json|safe }}
</script>
<script id="epsActual" type="application/json">
{{ eps_actual_json|safe }}
</script>
<script id="marketCap" type="application/json">
{{ market_cap_json|safe }}
</script>
https://code.jquery.com/jquery-3.5.1.min.js
https://cdn.datatables.net/1.10.21/js/jquery.dataTables.min.js
https://cdn.datatables.net/1.10.21/js/dataTables.bootstrap4.min.js
https://cdn.datatables.net/buttons/1.7.1/js/dataTables.buttons.min.js
https://cdn.datatables.net/buttons/1.7.1/js/buttons.bootstrap4.min.js
https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.3/jszip.min.js
https://cdn.datatables.net/buttons/1.7.1/js/buttons.html5.min.js
https://cdn.datatables.net/buttons/1.7.1/js/buttons.print.min.js
https://cdn.anychart.com/releases/8.10.0/js/anychart-bundle.min.js
<script>
$(document).ready(function () {
$('#historicalTable').DataTable({
paging: true,
searching: true,
ordering: true,
info: true,
lengthMenu: [10, 25, 50, 100],
order: [[4, "desc"]],
dom: 'Bfrtip',
buttons: [
{
extend: 'excel',
text: 'Export to Excel'
},
{
extend: 'print',
text: 'Print'
}
]
});
});
document.addEventListener("DOMContentLoaded", function () {
const dividendsPaidRaw = document.getElementById("dividendsPaid").textContent;
const epsActualRaw = document.getElementById("epsActual").textContent;
const marketCapRaw = document.getElementById("marketCap").textContent;
const dividendsPaid = JSON.parse(dividendsPaidRaw);
const epsActual = JSON.parse(epsActualRaw);
const marketCap = JSON.parse(marketCapRaw);
anychart.onDocumentReady(function () {
const rawData = document.getElementById("historicalData").textContent;
const historicalData = JSON.parse(rawData);
const candlestickData = historicalData.map(entry => [
entry.timestamp,
entry.open,
entry.high,
entry.low,
entry.close
]);
const table = anychart.data.table();
table.addData(candlestickData);
const candlestickMapping = table.mapAs({
open: 1,
high: 2,
low: 3,
close: 4
});
const candlestickChart = anychart.stock();
const candlestickPlot = candlestickChart.plot(0);
candlestickPlot.candlestick(candlestickMapping).name("Price");
candlestickChart.container("candlestickChart");
candlestickChart.draw();
// Dividends Paid Chart
const dividendsChart = anychart.line();
const dividendsData = dividendsPaid.map(entry => [entry.date, parseFloat(entry.value)]);
dividendsChart.data(dividendsData);
dividendsChart.title("Dividends Paid");
dividendsChart.xAxis().title("Date");
dividendsChart.yAxis().title("Dividends Paid");
dividendsChart.container("dividendsChart");
dividendsChart.draw();
// EPS Actual Chart
const epsChart = anychart.line();
const epsData = epsActual.map(entry => [entry.date, parseFloat(entry.value)]);
epsChart.data(epsData);
epsChart.title("Earnings Per Share (EPS)");
epsChart.xAxis().title("Date");
epsChart.yAxis().title("EPS");
epsChart.container("epsChart");
epsChart.draw();
// Market Cap Chart
const marketCapChart = anychart.line();
const marketCapData = marketCap.map(entry => [entry.date, parseFloat(entry.value)]);
marketCapChart.data(marketCapData);
marketCapChart.title("Market Capitalistion");
marketCapChart.xAxis().title("Date");
marketCapChart.yAxis().title("Market Cap");
marketCapChart.container("marketCapChart");
marketCapChart.draw();
});
});
</script>
</body>
</html>
Summary
This is the conclusion of the Python Django tutorial series. I hope you found it informative and useful.