Improving the Financial Trading Dashboard Using Python Django

How to enhance a financial trading dashboard by adding market search, historical data viewing, and improving functionality with Python Django.

EODHD APIs
7 min readDec 16, 2024
Enhancing the Financial Trading Dashboard with Python Django

This article builds on the previous one, Create a Financial Trading Dashboard Using Python and Django”, where we introduced the Django framework and created our first project and application. That tutorial demonstrated how to build a treemap of market indices with functionality to drill down and view their constituents using Indices Historical Constituents Data API from EODHD APIs.

In this article, we will enhance the application by making minor improvements, such as decoupling hardcoded API keys from the code. Additionally, we’ll add features to view historical market data and enable a market search.

Since these new features are closely related to the previous article, we will continue using the existing “spglobal” application rather than creating a new one within the Django project.

Indices Componets Table

Decoupling the API Keys

There are multiple methods to achieve this, but we will demonstrate “the Django way”.

The first step is to create a .env file in the root of the project, which should look like this:

EODHD_API_TOKEN=your_api_key

We will use the python-decouple library to load the .env file, enabling access to its contents within the application.

python3 -m pip install python-decouple -U

Modify the file “eodhd_apis/eodhd_apis/settings.py” by applying the following changes.

from pathlib import Path
from decouple import config


# Load the API token from the .env file
EODHD_API_TOKEN = config('EODHD_API_TOKEN')

Update the file “eodhd_apis/spglobal/views.py” to match the following.

from django.conf import settings
import requests
from django.shortcuts import render, get_object_or_404, redirect
from .models import SPGlobalIndex, IndexConstituent


def fetch_data(request):
api_token = settings.EODHD_API_TOKEN
url = f"https://eodhd.com/api/mp/unicornbay/spglobal/list?api_token={api_token}"
response = requests.get(url)
data = response.json()

for item in data:
SPGlobalIndex.objects.update_or_create(
index_id=item.get("ID"),
defaults={
"code": item.get("Code"),
"name": item.get("Name"),
"constituents": item.get("Constituents"),
"value": item.get("Value"),
"market_cap": item.get("MarketCap"),
"divisor": item.get("Divisor"),
"daily_return": item.get("DailyReturn"),
"dividend": item.get("Dividend"),
"adjusted_market_cap": item.get("AdjustedMarketCap"),
"adjusted_divisor": item.get("AdjustedDivisor"),
"adjusted_constituents": item.get("AdjustedConstituents"),
"currency_code": item.get("CurrencyCode"),
"currency_name": item.get("CurrencyName"),
"currency_symbol": item.get("CurrencySymbol"),
"last_update": item.get("LastUpdate"),
},
)

indices = SPGlobalIndex.objects.all()
return render(request, "spglobal/index.html", {"indices": indices})


def fetch_index_constituents(request, index_code):
api_token = settings.EODHD_API_TOKEN
url = f'https://eodhd.com/api/mp/unicornbay/spglobal/comp/{index_code}?fmt=json&api_token={api_token}'
response = requests.get(url)
data = response.json()

# Extract constituents and general information
constituents = data['Components'].values()
general_info = data['General']

return render(request, 'spglobal/constituents.html', {
'constituents': constituents,
'general_info': general_info
})

When the application is run again, it will function as before. The key improvement is that your private API key is now securely stored in the .env file.

Market Search API

EODHD APIs provides a search API that we aim to implement in this application. The official documentation can be found online.

A new data model is required for this feature. Unlike the previous models, this one will not store data in the database but will instead provide a structure to hold the API response temporarily.

Additionally, we will create a model for the new historical data feature. Update the file “eodhd_apis/spglobal/models.py” to include the required model.

# ADD THIS AT THE TOP

from dataclasses import dataclass


# ADD THIS AT THE END

@dataclass
class SearchData:
Code: str
Exchange: str
Name: str
Type: str
Country: str
Currency: str
ISIN: str
previousClose: float
previousCloseDate: str

Following the same process as before, you will need to update the urls.py and views.py files, and create a new template named search_results.html.

File to update: eodhd_apis/spglobal/urls.py.

from django.urls import path
from . import views

urlpatterns = [
path("", views.fetch_data, name="fetch_data"),
path(
"constituents/<str:index_code>/",
views.fetch_index_constituents,
name="fetch_index_constituents",
),
path("search/", views.search_market_data, name="search_market_data"),
]

eodhd_apis/spglobal/views.py

import time
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, SearchData


def fetch_data(request):
api_token = settings.EODHD_API_TOKEN
url = f"https://eodhd.com/api/mp/unicornbay/spglobal/list?api_token={api_token}"
response = requests.get(url)
data = response.json()

for item in data:
SPGlobalIndex.objects.update_or_create(
index_id=item.get("ID"),
defaults={
"code": item.get("Code"),
"name": item.get("Name"),
"constituents": item.get("Constituents"),
"value": item.get("Value"),
"market_cap": item.get("MarketCap"),
"divisor": item.get("Divisor"),
"daily_return": item.get("DailyReturn"),
"dividend": item.get("Dividend"),
"adjusted_market_cap": item.get("AdjustedMarketCap"),
"adjusted_divisor": item.get("AdjustedDivisor"),
"adjusted_constituents": item.get("AdjustedConstituents"),
"currency_code": item.get("CurrencyCode"),
"currency_name": item.get("CurrencyName"),
"currency_symbol": item.get("CurrencySymbol"),
"last_update": item.get("LastUpdate"),
},
)

indices = SPGlobalIndex.objects.all()
return render(request, "spglobal/index.html", {"indices": indices})


def fetch_index_constituents(request, index_code):
api_token = settings.EODHD_API_TOKEN
url = f"https://eodhd.com/api/mp/unicornbay/spglobal/comp/{index_code}?fmt=json&api_token={api_token}"
response = requests.get(url)
data = response.json()

constituents = data["Components"].values()
general_info = data["General"]

return render(
request,
"spglobal/constituents.html",
{"constituents": constituents, "general_info": general_info},
)


def search_market_data(request):
query = request.GET.get("query", "")
api_token = settings.EODHD_API_TOKEN
url = f"https://eodhd.com/api/search/{query}?api_token={api_token}&fmt=json"

response = requests.get(url)
data = response.json() if response.status_code == 200 else []

results = [SearchData(**item) for item in data]

return render(
request, "spglobal/search_results.html", {"results": results, "query": query}
)

eodhd_apis/spglobal/templates/spglobal/search_results.html

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Search Results</title>

<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
<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 a {
text-decoration: underline !important;
}

.table th,
.table td {
color: #ffffff;
}

.btn-dark {
background-color: #6c757d;
border-color: #6c757d;
}

a {
color: #ffffff !important;
text-decoration: none;
background-color: transparent;
}

a:hover {
color: #adb5bd !important;
}

.page-item.active .page-link {
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;
}
</style>
</head>

<body>
<div class="container mt-5">
<h1 class="mb-4 text-light">Search Results</h1>

<div class="table-responsive">
<table id="resultsTable" class="table table-dark table-striped table-bordered">
<thead class="thead-dark">
<tr>
<th>Code</th>
<th>Exchange</th>
<th>Name</th>
<th>Type</th>
<th>Country</th>
<th>Currency</th>
<th>ISIN</th>
<th>Previous Close</th>
<th>Previous Close Date</th>
</tr>
</thead>
<tbody>
{% for item in results %}
<tr>
<td>
<a href="/historical/{{ item.Code }}.{{ item.Exchange }}/d/" class="text-white">
{{ item.Code }}
</a>
</td>
<td>{{ item.Exchange }}</td>
<td>{{ item.Name }}</td>
<td>{{ item.Type }}</td>
<td>{{ item.Country }}</td>
<td>{{ item.Currency }}</td>
<td>{{ item.ISIN }}</td>
<td>{{ item.previousClose }}</td>
<td>{{ item.previousCloseDate }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>

<a href="{% url 'fetch_data' %}" class="btn btn-dark mt-4">Back to Index</a>
</div>

https://code.jquery.com/jquery-3.5.1.min.js
</body>

</html>

This will maintain the same look and feel as the constituents table described in the previous article.

Indices Constituents Table S&P 500 Historical Data

We will update the constituents page to enable navigation to this new page. The exciting part is that it works for all markets available on EODHD. By using the EODHD API code for any market with historical data, you can access and view it.

Updating the Constituents Results

To enhance functionality, it would be great to access either the market constituents or the historical data directly from the treemap. These updates have been made in the “eodhd_apis/spglobal/templates/spglobal/constituents.html” file.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>{{ general_info.Name }} Constituents</title>

<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
<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;
}
.btn-dark {
background-color: #6c757d;
border-color: #6c757d;
}
a {
color: #ffffff !important;
text-decoration: none;
background-color: transparent;
}
a:hover {
color: #adb5bd !important;
}
.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;
}
.code-link {
color: #ffffff;
text-decoration: underline !important;
}
.code-link:hover {
color: #adb5bd;
text-decoration: none !important;
}
</style>
</head>
<body>
<div class="container mt-5">
<h1 class="mb-4 text-light">{{ general_info.Name }} ({{ general_info.Code }}) Constituents</h1>

<table id="constituentsTable" class="table table-dark table-striped table-bordered">
<thead class="thead-dark">
<tr>
<th>Code</th>
<th>Name</th>
<th>Sector</th>
<th>Industry</th>
<th>Weight</th>
</tr>
</thead>
<tbody>
{% for constituent in constituents %}
<tr>
<td>
<a href="/historical/{{ constituent.Code }}.US/d/" class="code-link">
{{ constituent.Code }}
</a>
</td>
<td>{{ constituent.Name }}</td>
<td>{{ constituent.Sector }}</td>
<td>{{ constituent.Industry }}</td>
<td>{{ constituent.Weight }}</td>
</tr>
{% endfor %}
</tbody>
</table>

<a href="{% url 'fetch_data' %}" class="btn btn-dark mt-4">Back to Index List</a>
</div>

https://code.jquery.com/jquery-3.5.1.min.js
https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.1/dist/umd/popper.min.js
https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.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

<script>
$(document).ready(function() {
$('#constituentsTable').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'
}
]
});
});
</script>
</body>
</html>

“Gotcha”

You may notice that the constituents page returns a code like AAPL, but the historical data API requires the exchange suffix, such as .US. This information is not provided in the constituents list. To address this, we append .US to the code. This approach should work in nearly all cases. However, an issue could arise if an index includes cryptocurrencies, which use the .CC prefix.

Extensive testing has shown that the .US prefix works in most scenarios, but it’s important to be aware of this limitation. If EODHD APIs decide to include the exchange information in the constituents results, this issue can be resolved easily.

Next Steps

The next article will explore alternatives to D3.js for creating treemaps.

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.

--

--

EODHD APIs
EODHD APIs

Written by EODHD APIs

eodhd.com — stock market fundamental and historical prices API for stocks, ETFs, mutual funds and bonds all over the world.

No responses yet