Fundamental Data in the Financial Trading Dashboard with Python Django
How to integrate fundamental data, stock charts, and tables into a financial trading dashboard
This article continues the series, building on “Create a Financial Trading Dashboard Using Python and Django”, “Improving the Financial Trading Dashboard Using Python Django”, and “Integrating AnyChart into the Financial Trading Dashboard Using Python Django”.
The EODHD APIs feature an endpoint for Fundamental Data, detailed in their documentation. For the Django application, we focus on extracting five specific sections.
{
"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 available data. Additionally, we aim to retrieve Open, High, Close, Previous Close, and Volume, which are accessible via the EOD endpoint, as documented.
Finally, we will also retrieve the market capitalization for the market using another API endpoint, which is documented separately.
When visualized, the data will appear as follows…
We currently have a view that provides data for the AnyChart stock and the Bootstrap data table. Our goal was to integrate the fundamental and market cap data into the same page by enhancing the existing view.
To maintain performance and avoid chaining four sequential API calls, we utilized the asyncio
library and converted the view into an asynchronous function. This approach allowed us to dispatch the API requests simultaneously. Once all the data was retrieved, we processed it as required and returned it to the template as usual.
Updating the View — fetch_historical_data
The first step is to import asyncio
and httpx
into the “views.py” file.
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” function in “views.py” now appears as follows:
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")
),
},
)
Key Points to Note
- Asynchronous Function:
The function has been updated to “async def” to enable asynchronous processing. - Endpoints:
historical_url
: Endpoint to retrieve historical data.fundamental_url
: Endpoint to retrieve fundamental data.ohlc_url
: Endpoint to retrieve OHLC data (Open, High, Low, Close).market_cap_url
: Endpoint to retrieve market cap data.
3. Date Filtering:
The “today_minus_7_days” logic ensures we only retrieve the last 7 days of data, as the EOD API returns more data than required by default. From this, we use only the last 2 days for the application. This approach balances efficiency and reliability.
4. Return Updates:
The return statement has been updated to include all necessary information for the “historical_data.html” template.
5. Django’s Structure:
One of the strengths of Django is its well-defined structure. With a properly set up application structure, modifications and expansions can be made efficiently.
Updating the Template — historical_data.html
The historical_data.html template has been updated to include three sections:
- Fundamental Data: Displays the retrieved fundamental information.
- AnyChart Stock Chart: Visualizes the stock data interactively.
- Bootstrap Data Table: Presents the historical data in a structured tabular format.
The updated template 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 concludes the Python Django tutorial series. We hope you found the content informative and valuable for your projects.
Please note that this article is for informational purposes only and should not be taken as financial advice. We do not bear responsibility for any trading decisions made based on the content of this article. Readers are advised to conduct their own research or consult with a qualified financial professional before making any investment decisions.
For those eager to delve deeper into such insightful articles and broaden their understanding of different strategies in financial markets, we invite you to follow our account and subscribe for email notifications.
Stay tuned for more valuable articles that aim to enhance your data science skills and market analysis capabilities.