Integrating AnyChart into the Financial Trading Dashboard Using Python Django
How to integrate AnyChart into a financial trading dashboard for interactive stock charts and enhanced data visualization.
This article builds on the previous two in the series, “Create a Financial Trading Dashboard Using Python and Django” and “Improving the Financial Trading Dashboard Using Python Django”.
Initially, we used the D3.js library to create a treemap for the landing page. While it performed well, we decided to explore other charting libraries and evaluated both Chart.js and AnyChart. Ultimately, we opted to migrate the treemap from D3.js to AnyChart. While the visual output of both libraries is similar, AnyChart’s code proved to be more intuitive and easier to work with. Additionally, the AnyChart treemap offers slightly enhanced functionality and a polished appearance, though Chart.js remains a strong alternative with appealing aesthetics.
In the previous article, we introduced a page showcasing historical market data using a Bootstrap data table. For this article, we added an engaging stock chart above the table. After reviewing the same three charting libraries, we found AnyChart’s data presentation and functionality to be the most impressive.
This article details how we integrated AnyChart into the financial trading dashboard to enhance its functionality and visual appeal.
Finally, we explored another useful feature available in Bootstrap. In the previous article, we demonstrated how to add an “Export to Excel” button. Similarly, with just a single line of code, a “Print” button can be added. This feature formats the data from the Bootstrap table into a print-friendly layout.
Updating the View
We only needed to make one modification to the view to enable the historical data stock chart functionality.
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"
if interval not in ["m", "w", "d"]:
url = f"https://eodhd.com/api/{endpoint}/{market}?{interval_type}={interval}&from={start_date_unix}&to={end_date_unix}&api_token={settings.EODHD_API_TOKEN}&fmt=json"
else:
url = f"https://eodhd.com/api/{endpoint}/{market}?{interval_type}={interval}&from={start_date}&to={end_date}&api_token={settings.EODHD_API_TOKEN}&fmt=json"
print(url)
response = requests.get(url)
data = response.json()
def format_unix_timestamp(unix_ts):
return datetime.utcfromtimestamp(unix_ts).strftime("%Y-%m-%d %H:%M:%S")
for entry in 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 data or "error" in data:
data = []
raw_data = data
historical_data_json = json.dumps(data)
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
"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")
),
},
)
If you pay close attention to the function’s output, you’ll notice that we separated the data into two parts. The first, “historical_data”, contains the raw data returned by the API, which is used for the Bootstrap data table. The second, “historical_data_json”, is a sanitized version of the data in JSON format, required for the AnyChart stock chart.
Getting this to work was quite challenging. We wanted to provide two ways of visualizing the historical data, but each required the data in a different format. This approach proved to be an effective solution.
Updating the Template — index.html
As mentioned earlier, our initial treemap used the D3.js library. After evaluating both the Chart.js and AnyChart libraries, we found AnyChart to be more intuitive and polished. The converted code is provided below.
GOTCHA
We encountered a strange bug that puzzled us for several days. When the treemap code was first converted from D3.js to AnyChart, it worked flawlessly. However, after focusing on the Historical Data stock chart and returning to the treemap, it stopped rendering properly. While the API retrieved data for 110 market indices, only 11 were displayed.
To debug, we stripped the code down to its simplest components. The issue was traced back to the daily_return
value used for the treemap. Treemaps require positive numbers to render correctly. Initially, market conditions were favorable, and all daily returns were positive. A few days later, negative returns caused the rendering issue.
To resolve this, we ensured the value passed to the treemap was always absolute (positive). The daily_return
value was removed from the cell display and added to a tooltip shown on hover. This approach allowed the treemap to render correctly while maintaining a visually appealing gradient.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Market Indices Treemap</title>
https://cdn.anychart.com/releases/8.11.0/js/anychart-bundle.min.js
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
<style>
body {
background-color: #343a40;
color: #ffffff;
}
#treemap {
width: 100%;
height: 80vh;
margin: 0 auto;
}
</style>
</head>
<body>
<div class="container mt-5">
<form action="{% url 'search_market_data' %}" method="get" class="d-flex justify-content-center my-4">
<input type="text" name="query" class="form-control w-50" placeholder="Search market data..." required>
<button type="submit" class="btn btn-primary ml-2">Search</button>
</form>
<div id="treemap"></div>
</div>
<script>
const data = [
{
name: "Market Indices",
children: [
{% for index in indices %}
{
name: "{{ index.code }} [{{ index.constituents }}]",
value: Math.abs(parseFloat("{{ index.daily_return|default:0 }}")) || 0,
originalValue: parseFloat("{{ index.daily_return|default:0 }}"),
id: {{ forloop.counter }},
link: "/constituents/{{ index.index_id }}/",
description: "Constituents: {{ index.constituents }}"
},
{% endfor %}
]
}
];
anychart.onDocumentReady(function() {
const chart = anychart.treeMap(data, "as-tree");
chart.background().fill("#343a40");
const colorScale = anychart.scales.linearColor();
colorScale.colors(["#d73027", "#fee08b", "#1a9850"]);
chart.colorScale(colorScale);
chart.labels()
.fontColor("#ffffff")
.fontSize(function() {
return this.getData("value") > 100 ? 16 : 12;
})
.hAlign("center")
.vAlign("middle")
.format("{%name}");
chart.tooltip()
.useHtml(true)
.format(function() {
return `<strong>${this.getData('name')}</strong><br>
Value: ${this.getData('originalValue')}<br>
Link: <a href="${this.getData('link')}" target="_blank">${this.getData('link')}</a>`;
});
chart.hovered()
.fill(anychart.color.lighten("#007bff", 0.4))
.stroke("#d3d3d3");
chart.listen("pointClick", function(e) {
const link = e.point.get("link");
if (link) {
window.location.href = link;
}
});
chart.maxDepth(null);
chart.padding(10, 10, 10, 10);
chart.stroke("#ffffff");
chart.minHeight(5);
chart.minWidth(5);
chart.labels().fontSize(11);
chart.data(data);
chart.container("treemap");
chart.draw();
});
</script>
</body>
</html>
Updating the Template — historical_data.html
The next step was to add the AnyChart stock chart above the historical data Bootstrap table. Additionally, we included a “Print” button, which could be quite handy.
Both Chart.js and AnyChart offered feature-rich, visually appealing graphs. We chose AnyChart, not only to avoid mixing libraries within the app but also because we preferred its graphical presentation.
What’s especially nice about the AnyChart graph is its interactivity. Users can pan, zoom, and hover over data points for additional information. The chart visually represents candlesticks, similar to most trading applications. A green bar indicates the close price is higher than the open, while a red bar indicates the close price is lower than the open.
<!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-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">
<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>
<script id="historicalData" type="application/json">
{{ historical_data_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 rawData = document.getElementById("historicalData").textContent;
const historicalData = JSON.parse(rawData);
const chartData = historicalData.map(entry => [
entry.timestamp,
entry.open,
entry.high,
entry.low,
entry.close
]);
anychart.onDocumentReady(function () {
const chart = anychart.stock();
const table = anychart.data.table();
table.addData(chartData);
const mapping = table.mapAs({
open: 1,
high: 2,
low: 3,
close: 4
});
const plot = chart.plot(0);
plot.candlestick(mapping).name('Price');
chart.container("candlestickChart");
chart.draw();
});
});
</script>
</body>
</html>
Summary
We found AnyChart’s code to be straightforward and easy to work with. However, the graphs display a “trial version” watermark. Purchasing a license removes this watermark and provides additional support. Despite the watermark, the trial version has worked well for our purposes.
Next Steps
The next article in this series will focus on incorporating fundamental data and market capitalization into the application.
Subscribe and don’t miss it!
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.