It's easy to write your own indicators for FX Blue charts. User-defined indicators (UDIs) are just Javascript. You can use any editor of your choice, from Notepad to Eclipse or Visual Studio. A UDI typically has only two simple functions, and you can use all the features of the Javascript language. For large complex projects, you can even use an environment such as Typescript or Coffeescript which compiles to produce Javascript.
UDIs can do all the same things as the built-in indicators:
Plot values in a variety of styles, such as lines, points, histograms, channels, and candles
Create drawings on the chart, such as rectangles and lines
Create event markers
Create bar highlights (changing the colours of specific candles on the chart)
UDIs are web workers and have access to all the standard features of the browser's Javascript environment. For example, they can set timers, and they can read external data such as an economic calendar or trading signals, using XMLHttpRequest or web sockets.
You can debug your UDI using the features built into your browser, such as Chrome's Developer Tools.
There are several example UDI files. You can download them from the following URL:
https://www.fxblue.com/figaro/udi/udi-examples.zip
It's possible to learn how to write UDIs just from these example files, without reading this document. The number at the start of each file, e.g. 01-udi-basic-sma.js, is the suggested order for working through the examples.
You add a UDI to a chart - including any of the examples - by clicking on the indicators button in the chart toolbar, choosing Advanced / UDI, and then pasting in the Javascript code.
You can also store your UDI on a web server, and then paste in the URL of the file rather than its contents. Note: your server must be CORS-friendly; your server must set the CORS headers
If you want to share your UDI with other people while protecting how it does its calculation, you can use a code obfuscation service. Examples include javascript-obfuscator (free) and JScrambler (paid).
The UDI is initialised once, when it is loaded. The
If your UDI needs to react to changes in the choice of market or timeframe (beyond just recalculating all its data, which will happen anyway) then you can watch for these changes by implementing the optional functions UDI.onContextChange() and UDI.onParameterChange()
Your UDI automatically participates in the chart's alerting system. The user can set alerts on any of your indicator's plots, unless you set the noAlerts property.
Your UDI automatically participates in the chart's ability to apply one indicator to another indicator (unless you turn this off using the noIOI setting).
Another indicator - for example, a moving average - can be applied to any of the plots of your indicator. And, if your indicator has a Source field, then it can receive the values of another indicator rather than candle data such as close prices.
A UDI runs as a web worker, in its own browser thread. It does not have access to the HTML of the chart or the browser DOM. You interact with the chart using the API described in this document and in the example code.
Because it's a web worker, your UDI can use Javascript's importScripts() to include external code libraries.
All dates which you need to pass to the chart, such as co-ordinates for creating a drawing or an event marker, should be transmitted as a number of milliseconds (since 1/1/1970, rather than a textual date).
The dates used on the chart depend on the time zone, which is user-configurable. If you need to convert from an external source of dates, e.g. an economic calendar which uses GMT, then you can use the helper functions described below.
A UDI must have a
An example of the
UDI.onInit = function(data)
{
return {
// Display-name for the indicator
caption: "My SMA",
// Specifies that it should be in the main panel, not its own sub-panel
isOverlay: true,
// List of things which the indicator draws: just a line in this basic example
plots: [
{type: "line", caption: "avg", color: "blue", lineWidth: 2}
],
// Settings fields which the indicator wants.
settingsFields: [
// The "Source" field has special meaning, and the chart automatically
// assigns a caption to this field
{id: "Source"},
// Also require a number of bars for the average
{id: "period", caption: "Period", type: "int", defaultValue: 14, min: 2}
]
};
};
The return value from
Member | Description |
caption | The name to display for your UDI |
isOverlay | True/false depending on whether your UDI should be shown in the main chart panel (true) or in its own sub-panel (false) |
Array describing the types of values which your UDI draws: lines, histograms, bars etc. The array can be empty if your UDI only creates drawings, or event markers, or bar highlights. | |
Array of user-configurable parameters which your UDI wants. This array can be empty if your indicator does not need any configurable settings. | |
noAlerts | Can be used to turn off the ability for the user to set alerts on any of the UDI's plots. You can also disable this for each individual plot, using its own noAlerts property. |
noIOI | Can be used to prevent your UDI participating in the ability to apply one indicator to another indicator. |
axis | Overrides the default settings for the value axis, by including any or all of the following properties. Only applicable if the indicator is in its own sub-panel (isOverlay:false). |
axis.min | Minimum value for the axis |
axis.max | Maximum value for the axis |
axis.step | Increment between markers on the axis |
axis.noYGrid | Turns off horizontal grid lines in the sub-panel |
axis.baseline | Baseline value (which turns a line plot into a filled area either side of the baseline) |
The
Your UDI can draw any number of different plots. For example, to draw something like the standard Bollinger Band indicator, you would use 4 plots: a channel (for the filled high-low range) plus 3 line plots for the centre line, the upper line, and the lower line.
Each plot in the array must have a
Plot type | Values | Description |
line | 1 | Simple line |
point | 1 | Individual points rather than a continuous line |
histogram | 1 | Histogram; column starting at zero |
histogramPositiveNegative | 1 | Histogram with different colours for >0 and <0 |
floatingHistogram | 2 | Histogram from X to Y, rather than 0 to Y (first series is the upper value; second series is the lower value) |
channel | 2 | Filled channel (first series is the upper value; second series is the lower value) |
candles | 4 | Candles (with open, high, low, close) |
The full details of each plot are as follows. The only compulsory properties which you must provide are
Property | Description |
caption | The display name for the plot |
type | The type of plot; one of the values listed above |
noAlerts | Prevent users being able to set alerts on the value of this plot. (Not applicable to point plots; always true.) |
color | HTML colour code for the plot, e.g. "red", "#FF0000", "rgba(255,0,0,0.5)". For |
lineWidth | Only applicable to line or point plots. Width of the line/marker to draw. |
lineStyle | Only applicable to line or point plots. Style for the marker/line: "solid", "dash", "dot". It can also be any character from the Font Awesome font, in the form "icon-aaaa" where "aaaa" is the hex code of the character, such as "f0ab". |
markerOffset | Only applicable to point plots. Offset in pixels for displaying the marker. A negative value is above the price; a positive value is below the price. See the example fractal UDI for a typical use of the markerOffset, displaying markers above the high of a bar or below its low. |
The
There is one special field which fundamentally affects the type and behaviour of your UDI. If your indicator has a
The
settingsFields: [
{id: "Source"}
…
]
All other fields in the
settingsFields: [
{id: "Source"},
{id: "threshold", caption: "Threshold", type: "float", defaultValue: 1, min: 1, step: 0.1},
{id: "highlightcolor", caption: "Color", type: "color", defaultValue: "rgba(0,0,255,0.5)"},
{id: "options", caption: "Display type", type: "select", options: [
{k: "line", v: "Display as line"},
{k: "marker", v: "Display as histogram"}
]}
]
The types of field which you can use are as follows:
Field type | Description |
int | Integer field |
float | Numeric, non-integer field |
yesno | Yes/no (Boolean) field |
select | List of options |
textline | Free-text field |
color | Colour field |
maType | Type of moving average, for use with the UDI.movingAverage() helper function |
The full set of properties which can be defined for each field is as follows:
Property | Description |
id | ID for your field |
caption | Display text for your field |
type | The type of field: "int", "color", "textline" etc |
defaultValue | Default value for the field (depending on its type) |
omitFromParameters | Tells the chart that the field should not be displayed in the summary of the indicator's values |
min | For numeric fields, the minimum value which can be entered |
max | For numeric fields, the maximum value which can be entered |
step | For numeric fields, the increment between values |
options[] | For the |
The
The same context data is also passed to all other UDI functions, including UDI.onCalculate().
The properties in
Property | Description |
chartId | Permanent ID for the chart, persisting across different sessions and reloads |
chartInstanceId | Temporary ID for the chart, not persisting across sessions |
chartStyle | Descriptor for the type of chart: filled candle, hollow candle etc |
indicatorId | ID allocated to the instance of your UDI on the chart, persisting across sessions and reloads of the chart |
userId | Unique but anonymised ID for the logged-in user of the charting system |
instrument | Description of the selected instrument and timeframe of the chart, containing the following properties. |
instrument.symbol | ID for the selected market |
instrument.symbolCaption | Display name for the selected market |
instrument.timeframe | Timeframe of the chart, as either a number of seconds (e.g. 3600 for H1), or a negative value for a tick-chart (e.g. -30 for a T30 chart) |
instrument.pipSize | "Pip" size of the market, e.g. 0.0001 on EUR/USD |
instrument.multiplier | Multiplier applied to the chart prices, e.g. 10 |
instrument.invertPrices | Whether the user has selected to invert the chart prices |
instrument.bid | Current bid price |
instrument.ask | Current ask price |
instrument.tz | Object describing the user-selected time zone for the chart, with the following properties. |
instrument.tz.offsetMinutes | Base number of minutes ahead of GMT (e.g. 120 for GMT+2) |
instrument.tz.dstMode | Daylight savings schedule: 0 = none; 1 = USA; 2 = Europe; 3 = Australia |
Your UDI must have a
A simple example of the
UDI.onCalculate = function(data, output)
{
// Get the user-selected value of the "period" parameter
var period = data.parameters["period"];
// The data to be worked on is in the array data.valueData.
// We then put our output - the average - in output.values, which
// is an array of arrays, with entries depending on the number of plots
for (var i = period - 1; i < data.valueCount; i++) {
var sum = 0;
for (var j = 0; j < period; j++) sum += data.valueData[i - j];
output.values[0][i] = sum / period;
}
};
The
There are two types of UDI:
If your UDI has a Source field, then it receives a single series of input values, such as close prices or high prices, in
If your UDI does not have a
Your UDI can have settings fields, such as the number of bars for a moving average, and the current value of each of these settings is then available in the
Within
For example:
settingsFields: [
// Number of bars for the average
{id: "period", caption: "Period", type: "int", defaultValue: 14, min: 2}
]
…
UDI.onCalculate = function(data, output)
{
// Get the user-selected value of the "period" parameter
var period = data.parameters["period"];
…
}
As explained above, your UDI receives either a single array of input values, in
The
The
The
The input data (and also your output data) is indexed with the oldest item in [0], and the latest, current value in [length-1]. (If you are used to the MT4 or MT5 platforms, this is the opposite way round to what you may be familiar with.)
To make calculations faster and more efficient, the chart tells your UDI whether only the current bar has changed since the last calculation, using the
Typically, if
The example code contains two versions of an SMA indicator: one which always re-calculates all values, and a more efficient one which uses
You put the results of your UDI's calculations into the
The
plots: [
{type: "channel", …}, // Requires 2 output series
{type: "line", …}, // Requires 1 output series
{type: "candles", …} // Requires 4 output series
]
In this example,
Each sub-array inside
If
If the update is not just for the current bar only, then the contents of each sub-array in
There are circumstances where your indicator may need to re-plot its values without a new market price. For example, you may request external data, and then need to update your indicator's output when that data is received.
You can use the function
Many indicators involve calculating moving averages in order to prepare the indicator's final values. For example, a MACD indicator is based around the calculation of two moving averages, for its "fast" and "slow" periods.
The chart provides a helper function,
The
Parameter | Description |
maType | ID for the type of moving average: "sma", "hull" etc |
period | Parameter for the moving average (number of bars) |
inputData | Array of values to calculate from |
The output you get back is an array with the same number of elements as the
The available types of moving average, for the
Type | Description |
sma | Simple moving average (i.e. arithmetic mean of last N values) |
ema | Exponential moving average |
smma | Smoothed moving average |
wma | Weighted moving average |
tma | Triangular moving average |
vidya | Variable index dynamic average |
lsma | Least squares moving average |
hull | Hull moving average |
dema | Double exponential moving average |
tema | Triple exponential moving average |
mcginley | McGinley dynamic moving average |
If you want to make the type of average user-configurable, rather than hard-coded into your UDI, then you can include in your settingsFields[] an entry with its
settingsFields: [
…
{id: "ma", caption: "Moving Average Type", type: "maType"}
]
…
UDI.onCalculate = function(data, output)
{
// 20-bar average of chosen type
var arrAvg = UDI.movingAverage(data.parameters["ma"], 20, data.valueData);
…
}
If there is a change to the chart, such as a switch of timeframe, or a change in your user-defined settings, then the chart will always make your UDI recalculate all its data by issuing a call to UDI.onCalculate().
However, if you need to take special, extra action when these values/settings change, then you can implement the optional functions UDI.onContextChange() and UDI.onParameterChange()
For example:
UDI.onContextChange = function(data)
{
// Get current chart timeframe
var tf = data.context.instrument.timeframe;
};
For example:
settingsFields: [
// Number of bars for the average
{id: "period", caption: "Period", type: "int", defaultValue: 14, min: 2}
]
…
UDI.onParameterChange = function(data)
{
// Get the latest user-selected value of the "period" parameter
var period = data.parameters["period"];
…
}
In addition to plotting values, using the plots[] array and UDI.onCalculate(), your UDI can also create objects on the chart:
Drawings, such as rectangles, lines, bar markers, trend channels etc
Event markers. These are markers against a specific date/time, displayed at the bottom of the chart, and automatically stacked if there are multiple markers to be displayed against the same bar.
Bar highlights. These change the colour of a specific bar on the chart in order to highlight it.
Note: you cannot create these objects from
For examples of the event markers and bar highlights, see the built-in Inside Bars or Outside Bars indicators.
When you create one of these objects, it stays on the chart (until your UDI is removed). You do not need to re-create the object in each call to
Objects are not removed if the user changes the market or timeframe of a chart. You will often want to remove and re-create objects after this sort of change, which you can detect using UDI.onContextChange().
Objects can be created from any function in your UDI, not just in response to
UDI.onInit = function(data)
{
// Make a data request
var xmlr = new XMLHttpRequest();
xmlr.addEventListener("load", onMyDataLoad);
xmlr.open("GET", "http://www.example.org/mydata");
xmlr.send();
// Return indicator definition
return { … };
}
function onMyDataLoad()
{
// Parse this.responseText and create objects…
UDI.createDrawing(…);
}
Your UDI cannot see or manipulate objects which do not belong to it. You cannot modify or remove drawings, event markers, or bar highlights which were created by the user or by another indicator.
You create drawings using
UDI.createDrawing([
{type: "horizontalLine", points: [{value: 1.2345}], style: {line: {color: "red"}}},
{type: "horizontalLine", points: [{value: 1.3456}], style: {line: {color: "blue"}}}
]);
By default, the user can select a drawing and change its properties, but the user cannot move or delete the drawing. You can change this behaviour using the drawing's properties.
You can remove all your drawings from the chart using
You can also remove individual drawings, or you can reposition a drawing without deleting and recreating it. In order to do this, you need to get the ID of the drawing. These are provided via an optional asynchronous callback function which you can pass to
UDI.createDrawing([
{type: "horizontalLine", points: [{value: 1.2345}], style: {line: {color: "red"}}},
{type: "horizontalLine", points: [{value: 1.3456}], style: {line: {color: "blue"}}}
], function (response) {
// The ID of each drawing will be contained in the array response.drawingId[]
UDI.myLine1 = response.drawingId[0];
UDI.myLine2 = response.drawingId[1];
});
Once you have the ID of a drawing, you can delete it by passing the ID to
A drawing can be moved by passing an object containing the ID and a new array of points to
UDI.moveDrawing({
drawingId: …,
points[{date: data.barData.date[0]}, value: data.barData.high[0]}]
});
The properties of a drawing - text, colour etc - can be changed using
// Change drawing text and its colour
UDI.changeDrawing({
drawingId: …,
text: "New text",
style: {text: {color: "red"}}
});
Drawings can have the following properties. Each drawing must have a
Some properties are only applicable to some types of drawing (e.g. text, icon).
Property | Description |
type | Type of drawing, e.g. "lineSegment" |
points[] | Array of points, each one consisting of a For text and rectangle drawings, the points can also be pixel co-ordinates (from the top-left of the chart), instead of time-price co-ordinates. For example: |
style | Graphical style for the drawing, consisting of |
style.line | Can contain the properties |
style.fill | Can contain a |
style.text | Can contain the properties |
inMainPanel | Only applicable to indicators which are displayed in their own sub-panel (isOverlay:false). Controls whether the drawing is created in the indicator's sub-panel or in the main chart panel. |
unselectable | If true, makes the drawing completely unselectable (and unmoveable, and undeletable) |
moveable | If true, allows the user to move the drawing |
removable | If true, allows the user to delete the drawing |
showInBackground | Controls whether the drawing is shown in front of the bars, or behind them |
text | Text to display |
icon | Icon to display, as a hex code of a character from the Font Awesome character set, such as "f0ab" |
iconSize | Size, in pixels, for the icon |
markerOffset | For the |
textAboveLine | Boolean value which positions text above the centre of the drawing (e.g. above a horizontal line or a bar marker) rather than below it |
The types of drawing which you can create are as follows.
Type | Points | Description |
barMarker | 1 | Marker against a time and price, consisting of an icon and optional text |
text | 1 | Text label. (Note: you can also display text using a |
horizontalLine | 1 | Horizontal line (date component of point is ignored and not required) |
verticalLine | 1 | Vertical line (value component of point is ignored and not required) |
rectangle | 2 | Rectangle |
circle | 2 | Circle (the two co-ordinates are the centre and any point on the circumference) |
ellipse | 2 | Ellipse (the two co-ordinates are the centre and any point on the circumference) |
triangle | 3 | Triangle |
lineSegment | 2 | Line from one time/price to another time/price |
ray | 2 | Like |
diagonal | 2 | Like |
channel | 3 | Filled channel extending to the edge of the chart |
priceRange | 2 | Filled range covering the whole of the chart horizontally between two prices (date component of points is ignored and not required) |
dateRange | 2 | Filled range covering the whole of the chart vertically between two dates (date component of points is ignored and not required) |
Event markers are created against specific date/times, and drawn at the bottom of the chart. They are automatically stacked if there is more than one marker for each bar.
(Event markers are intended for things such as calendar events, which apply to a bar as a whole. To draw a marker against both a date/time and also a specific price, use a barMarker drawing.)
An event marker can have the following properties:
Property | Description |
date | Date for the event marker, as a number of milliseconds. Does not have to match the start time of a bar. Will be drawn against the bar containing the specified time. |
icon | Hex code of a character from the Font Awesome character set, such as "f0ab" |
priority | Priority of the event marker compared to other markers, on a numeric scale from 0 (lowest) to 2 (highest). Determines stacking order if there are multiple markers for a single bar |
color | Color for the marker, as an HTML colour code such as "red" or "#FF0000" or "rgba(255,0,0,0.5)" |
text | Text to display if the user clicks on the marker |
noLine | Specifies that no line should be drawn across the chart for the marker |
You create markers by passing an individual marker definition, or an array of definitions, to
UDI.createEventMarker([
{date: data.barData.date[i], color: "red", icon: "f0ab", text: "My marker 1"},
{date: data.barData.date[i], color: "green", icon: "f0aa", text: "My marker 2"}
]);
You can remove all your event markers from the chart using
You can also remove individual markers (rather than removing everything and re-creating different ones). The
UDI.createEventMarker([
{date: data.barData.date[i], color: "red", icon: "f0ab", text: "My marker 1"},
{date: data.barData.date[i], color: "green", icon: "f0aa", text: "My marker 2"}
], function (asyncResponse) {
// The IDs of the two markers will be contained in asyncResponse.markerId[]
UDI.storeMyUpMarker = asyncResponse.markerId[0];
UDI.storeMyDownMarker = asyncResponse.markerId[0];
});
When you have obtained the IDs using the asynchronous callback, you can then remove an individual marker by passing its ID to
Bar highlights change the colours of specific bars on the chart, in order to highlight them in some way.
The definition of a bar highlight simply has two properties:
Property | Description |
date | Date for the highlight, as a number of milliseconds. Does not have to match the start time of a bar. Will change the bar which contains the specified time. |
color | Color for the bar, as an HTML colour code such as "red" or "#FF0000" or "rgba(255,0,0,0.5)" |
You create bar highlights by passing an individual definition, or an array of definitions, to
UDI.createBarHighlight([
{date: data.barData.date[i - 1], color: "red"},
{date: data.barData.date[i - 2], color: "green"}
]);
You can remove all your highlights from the chart using
Individual highlights can be removed by passing a date, or an array of dates, to
UDI.removeBarHighlight([data.barData.date[i - 1], data.barData.date[i - 2]]);
Your UDI can collect external data, such as an economic calendar or trading signals, and then draw that data on the chart or incorporate it into the output which your indicator calculates.
Like any Javascript in a web browser, your UDI can use XMLHttpRequest (or web sockets).
However, as always,
If you are trying to use a third-party service which is not CORS-compatible then you need a proxy in between your UDI and the third-party server. For example, you cannot request the Forex Factory calendar at https://cdn-nfs.forexfactory.net/ff_calendar_thisweek.xml directly from browser Javascript, because the Forex Factory page does not set CORS headers.
If you cannot arrange a proxy of your own, FX Blue provides a shared (and free) proxy service. You need to request white-listing of the URL which you want to connect to, by emailing the details to support@fxblue.com. If accepted, we will send back an ID such as
// White-listed ID for your URL, provided to you by FX Blue
var strWhitelistId = "6c7e287b71544dd2a739ac94659f0324";
// Make proxy request, with callback to onMyDataLoad() when loaded
UDI.proxyRequest(strWhitelistId, onMyDataLoad);
…
function onMyDataLoad()
{
// Read XMLHttpRequest response in this.responseText
}
The FX Blue proxy is a shared service which caches data in order to reduce load and to prevent abuse. When we white-list your URL we will notify you of its caching period, such as 60 seconds. If you make more than one request within each 60 second period you will get back the previous cached data rather than a new request. You can test whether you have received cached data by inspecting the
function onMyDataLoad()
{
// Will return either 0 or 1
var isCached = this.getResponseHeader("X-FromCache");
…
}
The dates used on the chart depend on the time zone, which is user-configurable. For example, the default setting is GMT+2 with daylight savings on the USA schedule (so that fx markets always open at midnight on Monday).
The chart's time zone is provided via the context data which is available via the
// Get base offset in minutes, and daylight savings mode
var offsetMinutes = data.context.instrument.tz.offsetMinutes;
var dstMode = data.context.instrument.tz.dstMode;
You can convert from GMT to the chart time zone using
// Convert current time (from computer clock) to chart time
var offsetMinutes = data.context.instrument.tz.offsetMinutes;
var dstMode = data.context.instrument.tz.dstMode;
var now = new Date();
var adjustedDate = UDI.convertGMTToUserDate(now, offsetMinutes, dstMode);
console.log("Equivalent chart date to current time: " + (new Date(adjustedDate)));
You can convert in the opposite direction, from chart time to GMT, using
var offsetMinutes = data.context.instrument.tz.offsetMinutes;
var dstMode = data.context.instrument.tz.dstMode;
var latestChartTime = data.barData.date[data.valueCount - 1];
var adjustedDate = UDI.convertUserDateToGMT(latestChartTime, offsetMinutes, dstMode);
console.log("Equivalent local date to latest chart time: " + (new Date(adjustedDate)));