In this blog post we will build the financial chart that you see below. This is a candlestick chart created with MindFusion Charting for JavaScript. We use data from the platform TraderMade. The data comes in a continuous stream as a WebSocket. The blog post explains how to get the data, how to parse it and how to create and customize the chart. The chart also offers the user to change the data interval and to zoom in/out the chart.
I. Read and Parse the Data
We get the financial data from the platform TraderMade. We use the service that provides continuous stream of data through WebSockets.
In order to receive the data you need to set up NodeJS and install the websocket package. Both are beyond the scope of this blog post but you can find plenty of tutorials how to setup a NodeJS server and install npm packages online. If you have difficulties, you can always MindFusion technical support team and ask for help.
Once you have installed the WebSocket client, you are ready to start coding! Create an empty JavaScript file and initialize the WebSocket class:
const ws = new WebSocket('wss://marketdata.tradermade.com/feedadv'); ws.onopen = function () { ws.send('{"userKey":"your-key-here", "symbol":"EURUSD"}'); };
With those three rows of data you have connected to the tradermade server and requested that the data be served to you. Note that you have to give in your API key. Time to read and parse the data:
ws.onmessage = function (event) { if (event.data != "Connected") { var data = JSON.parse(event.data); rawData.push(data); } };
Note that if everything is successful and you do get data, it will be in a raw format. This means the values that the CandlestickChart expects are not contained per se. You will have to convert the data because this is the output you will receive running the code so far:
{"symbol":"EURUSD","ts":"1714750198601","bid":1.07707,"ask":1.07708,"mid":1.077075} {"symbol":"EURUSD","ts":"1714750199095","bid":1.07708,"ask":1.07709,"mid":1.077085} {"symbol":"EURUSD","ts":"1714750199141","bid":1.07709,"ask":1.07709,"mid":1.07709} {"symbol":"EURUSD","ts":"1714750199199","bid":1.07709,"ask":1.0771,"mid":1.077095} {"symbol":"EURUSD","ts":"1714750199210","bid":1.07709,"ask":1.07711,"mid":1.0771} {"symbol":"EURUSD","ts":"1714750199222","bid":1.0771,"ask":1.07711,"mid":1.077105} {"symbol":"EURUSD","ts":"1714750199227","bid":1.0771,"ask":1.07712,"mid":1.07711} ........................................... ...........................................
How do we convert this data into the usual high, low, start and end values? The tradermade website has documentation about it. Basically, we have to make some calculations on the raw data in order to extract and prepare the high, low, open, close value (HLOC). This is how you do it:
// Calculating HLOC Data function calculateHLOC(groupedData) { const hlocData = []; for (const intervalKey in groupedData) { const intervalData = groupedData[intervalKey]; var l = intervalData.length - 1; const open = intervalData[0].mid; const close = intervalData[l].mid; ...................... ...................... const date = intervalData[0].ts; hlocData.push([open, close, low, high, date]); } return hlocData; }
The full code is available with the project, which you can download from the link below. Before you reach the point to calculate the HLOC values however, you need to group the data. The method that does it is in the source code file.
So, once we’ve parsed and prepared the data as HLOC values we can create the StockPriceSeries objct, which is the data series expected by a CandlestickChart The StockPriceSeries class contains data from StockPrice objects. So, from the array with parsed and prepared HLOC data we create the StockPrice values this way:
var stockData = []; var hlocData = calculateHLOC(groupedData); hlocData.forEach(item => { //check to see if we aready have a candle with this timestamp let result = stockData.filter((s) => s[4] == item[4]); if(result.length == 0) stockData.push(item); });
Note the check where we look whether a data with the given timestamp is already added. This is important because the parsed stock data is continuously added to the rawData array and we want to avoid adding data we have already added for the second time.
Finally, we create the StockPrice object and add it to the StockPriceSeries var dataItem = new Charting.StockPrice(stockData[i][0], stockData[i][1], stockData[i][2], stockData[i][3], date);
dataList.add(dataItem);
Time to create the CandlestickChart and set the StockPriceSeries as its data source.
II. The Candlestick Financial Chart
The CandlestickChart needs a canvas object to render itself onto. We create an empty HTML file and add a Canvas to it. We give the Canvas an id:
<div style="position: absolute; left: 0px; top: 60px; bottom: 0px; right: 0px"> <canvas id="chart" style="width: 100%; height: 100%; display: block;"> </canvas> </div>
In the JavaScript code-begind file you need to add just this line of code to create the chart:
var chart = new Charting.Controls.CandlestickChart(document.getElementById("chart"));
You add the data series to the chart with the series property, which is of type ObservableCollection e.g. it supports adding multiple series. So, we create a collection and add the series to it. Then we assign it to the series property of the chart:
var data = new Collections.ObservableCollection(); data.add(series); chart.series = data; chart.draw();
It is important that you call the draw method, which signalizes to the control that the chart is initialized and rendering can start. If you open the file in the browser you already would be able to see the chart. We
III. Chart Appearance Customizations
The Charting library offers a long list of properties for making your chart look exactly the way you want it. We start by adjusting the title of our financial chart.
chart.title = "Wait 5 secs to fill data buffer"; chart.titleMargin = new Charting.Margins(0, 10, 0, 5); chart.titleBrush = new Drawing.Brush("#e0e9e9"); chart.titleFontStyle = Drawing.FontStyle.Italic | Drawing.FontStyle.Bold;
We want the title to be drawn in bold, italic font and we want to warn the user that they need to wait a bit before rendering starts. It’s also nice to give the title a bit of offset from the chart plot area.
The X-axis in candlestick charts is a plain numeric axis with interval of 1. The CandlestickChart control assign internally to each candle an integer incrementing each value with one and starting from 0. This means we can create the X-axis with intervals of 5 to render interval data at each 5 candle rather than with each one, which can make the axis overcrowded.
chart.xAxis.minValue = 0; chart.xAxis.interval = 5; chart.xAxis.maxValue = 50;
We also would like to hide the title of the axis and hide the coordinates (the numbers) as well as show ticks:
chart.showXCoordinates = false; chart.showXTicks = true; chart.xAxis.title = "";
We want to hide the title of the Y-axis, so we use its Title property and set it to an empty string.
IV. Customizing the Axis Labels
We want to render labels at both axes in a special manner. First, at the X-axis, we want to render a label at each fifth division. This means we will have four intervals without a label. We can achieve this by overriding the getLabel method of the StockPriceSeries class. This method is responsible for returning the correct label based on the LabelKinds value and the index of the element in the series. Note that the labels at the X-axis for candlestick charts are provided by the series, not the axis.
Here is how we override the getLabel function in order to have a label on each 5th element only:
Charting.StockPriceSeries.prototype.getLabel = function(index, kind) { if (kind == Charting.LabelKinds.XAxisLabel) { if(index % 5 == 0) return Charting.Utilities.formatDateTime(this.values.items()[index].date.valueOf(), this.dateTimeFormat, this.customDateTimeFormat, "", ""); return ""; } }
We want to provide special formatting to the numbers at the Y-axis as well. They are calculated by the YAxisRenderer object, which also has a getLabel method. In it we are going to return a number that is formatted to have 5 digits after the floating point.
Charting.YAxisRenderer.prototype.getLabel = function(index, value, context) { let axis = this.effectiveAxis(context); if (!this.pinLabels) return value.toFixed(5); let effectiveMin = axis.effectiveMinValue; let effectiveMax = axis.effectiveMaxValue; let interval = axis.interval || Math.abs(effectiveMax - effectiveMin) / 10; let num = effectiveMin + interval * index; return num.toFixed(5); }
Regarding the Y-axis, we would like to leave a little margin at the top or bottom at the margin regarding the data interval. This means we have to check dynamically if the stock data has a high or low value that goes too close to the start or end of the Y-axis.
if(stockData[i][2] - 0.00002 < min) { min = stockData[i][2] - 0.00002; chart.yAxis.minValue = min; } if(stockData[i][3] + 0.00002 > max) { max = stockData[i][3] + 0.00002; chart.yAxis.maxValue = max; }
When we do the check, we want to make sure there’s a little margin, so we add the value of 0.00002 to the calculations.
V. Adding Dynamics
Of course, we need to check for new data regularly, at a given time period. We set a timer to trigger the method that collects and parses financial data from the WebSockets.
setInterval(showData, 1000);
We set the timer to 1 second, which is the interval through which data is collected. This is not the interval, with which data is rendered to the user. The time span between candles is specified by the interval global variable and is used when data is grouped.
The interval is set in milliseconds. We start with an interval of 6 seconds, but this could be changed via the select control on top of the chart:
var interval = 1000 * 6;
The select control has various options and the value of each one specifies the interval, in seconds. When a change is triggered, we need to get the new value, multiply it by 1000 beause the WebSocket works with milliseconds, and then redraw the chart:
var cbInterval = document.getElementById('cbInterval'); cbInterval.addEventListener("change", function() { var coeff = cbInterval.value; interval = 1000 * coeff; setInterval(showData, 1000 * coeff); chart.draw(); });
VI. Zooming
We want to give our users the option to see a bigger or smaller time interval at the chart. The chart has built-in zoom when the user scrolls the mouse. However, we want the bars to get smaller when the interval gets bigger. That’s why we will implement our own custom zoom.
We add two images to the web page and handle their click events. Zooming is easy implemented: each time the user clicks an image, we increase or decrease the –minValue and maxValue of the X-axis. We also use the candlestickWidth property to adjust the candle width.
//html file //javascript file function zoomIn () { chart.xAxis.minValue += zoomStep; chart.xAxis.maxValue -= zoomStep; chart.candlestickWidth = chart.candlestickWidth * 1.2; chart.draw(); }
It’s important to redraw the chart after each method call in order to see the changes.
With that we have covered the most important points of building this practical and beautiful candlestick chart that you see at the beginning of the blog post. You can download the complete source code of the sample for your reference from this link:
If you need assistance with the code or your own project, please contact MindFusion support team or use the discussion board to post your questions. Either way our team shall be glad to help you.
About Charting for JavaScript: MindFusion library for interactive charts and gauges. It supports all common chart types including 3D bar charts. Charts can have a grid, a legend, unlimited number of axes and series. Scroll, zoom and pan are supported out of the box. You can easily create your own chart series by implementing the Series interface.
The gauges library is part of Charting for JavaScript. It supports oval and linear gauge with several types of labels and ticks. Various samples show you how the implement the gauges to create and customize all popular gauge types: car dashboard, clock, thermometer, compass etc. Learn more about Charting and Gauges for JavaScript at https://mindfusion.eu/javascript-chart.html.