It's easy to write your own indicators for Figaro 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. (It is also possible to write indicators in C# or Python, but these require a bridge application.)
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.
When a UDI is added to a chart, it can optionally also be given access to the Figaro scripting framework. This lets it see any framework data such as the order history, and lets it carry out actions such as placing trades. In Figaro's terminology, this turns the UDI into a UDIX.
Like anything with framework access, a UDIX can be "not just an indicator". The ability to place trades means that a UDIX can implement a trading algo, and using the indicator features to draw its activity on the chart.
This guide only covers the core indicator functionality of drawing on the trading chart. The scripting framework is described separately: https://www.fxblue.com/figaro/scripting/help
Without UDIX access, a UDI cannot place trades but it can suggest trades, and it can see account metrics and current order data by subscribing for this information.
There are several example UDI files. You can download them from the following URL:
https://www.fxblue.com/figaro/udi/figaro-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.
The code in the example files and this guide uses one convention, entirely optional. When the code wants to store a value for later use, across different calls to onCalculate(), it does so by storing the value in the indicator class object and it prefaces the name with $. For example, from the 04-udi-macd.js file:
this.$myFastMA = FXB.ta.CreateMovingAverage(maType, {period: fastPeriod});
The $ has no special meaning in Javascript. The example code does this for two reasons: (a) to make clear what is a built-in function or property of the object versus its own private data which the code is storing, and (b) to avoid any possible collision between names of the private variables and names of built-in properties or functions.
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 onContextChange() and 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 are represented as a number of milliseconds, not as Javascript date objects.
The time zone of a chart is user-configurable. The dates you receive, in data.dates or data.barData.date, are the user's chart time, not UTC.
Similarly, any dates which you need to pass back to the chart, such as co-ordinates for creating a drawing or an event marker, also need to be numbers of milliseconds representing chart time rather than UTC.
If you need to convert the chart times to UTC (or another time zone), then you can use the helper functions described below.
You implement your UDI by defining a class called
class MyIndicator extends UserDefinedIndicator {
onInit(data) {
…
}
onCalculate(data, output) {
…
}
}
Your class must be called
class SimpleMovingAverage extends UserDefinedIndicator {
onInit(data) { … }
onCalculate(data, output) { … }
etc
}
// In effect, make MyIndicator a synonym for SimpleMovingAverage
class MyIndicator extends SimpleMovingAverage {}
Your indicator class must implement onInit(), which tells the platform what your indicator draws and what parameters it wants.
You will almost always want to implement onCalculate(), which calculates the values for your indicator. (However,
There are further handlers which you can optionally implement such as onContextChange() or onToastClick().
Extending
Your class must have an
An example of the
onInit(data)
{
return {
// Display-name for the indicator
caption: "My SMA",
// Specifies that the indicator 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. | |
showSettingsAfterLoad | If true, tells the platform to open the settings dialog for your indicator after adding it to the chart. |
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 ( |
axis.digits | Number of decimal places to display in the axis values |
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) |
subscribeMetrics | Subscribes for account metrics data (if set to true) |
subscribeOrders | Subscribes for order data (if set to true) |
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.
Your
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 | 3 | Floating histogram from A to B, rather than 0 to B, and/or a coloured histogram. Uses three output buffers. The first two are the A and B values for the histogram. The third buffer contains colours, which can be left as null to use the default colour specified for the plot. |
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 |
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 FXB.ta library |
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 |
isStyle | Tells the platform that this field is a visual style, and should be displayed on the Styles tab of the indicator's configuration window rather than on the Settings tab. (Automatically true if |
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 select field-type only, an array of options for the user to select from. Each entry in the array should have a key, |
The
The same context data is also passed to all other UDI functions, including onCalculate().
The properties in
Property | Description |
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 |
timezone | Object describing the user-selected time zone for the chart, with the following properties. |
timezone.offset | Base number of minutes ahead of UTC (e.g. 120 for UTC+2) |
timezone.dstMode | Daylight savings schedule: 0 = none; 1 = USA; 2 = Europe; 3 = Australia |
isUDIX | Whether the UDI has been loaded with Framework access |
Your class will normally have an
A simple example of the
onCalculate(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 = 0; i < data.valueCount - period; 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}
]
…
onCalculate(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 newest, current value item in [0], and the oldest item in [length-1].
Note: date/time values, in either
There are three possible reasons for a call to your
There has been a major change to the available input data. (This includes initial load of your UDI.) You need to recalculate all your output, because any part of the input may have changed.
A new bar has started, and its value is being added to the input.
The value of the current bar [only] has changed.
The chart tells your UDI what is causing the call to
If only the current bar has changed, then
If a new bar has been added (and no previous data has changed), then
Otherwise, if neither of these is set, then any part of the historic data may have changed, and you need to recalculate all your output.
You can use this information to make your calculations faster and more efficient. 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
onCalculate(data, output)
{
// Get the user-selected value of the "period" parameter
var period = data.parameters.period;
if (!data.currentBarUpdateOnly) {
// Do a full recalculation of the values at each bar (repeating the example above)
for (var i = 0; i < data.valueCount - period; i++) {
var sum = 0;
for (var j = 0; j < period; j++) sum += data.valueData[i + j];
output.values[0][i] = sum / period;
}
} else {
// Only the current bar is changing, and we only need to recalculate
// the current indicator value. (Note: the 02-udi-efficient-sma.js example
// demonstrates a more efficient version of this which avoids having
// to use a loop.)
var sum = 0;
for (var j = 0; j < period; j++) sum += data.valueData[j];
output.values[0][0] = sum / period;
}
};
Although the framework pre-creates and pre-populates the arrays inside
// Create and store a moving average calculation. (The $ has no special meaning.
// It just makes clear what is a built-in UDI function/property and what is a private
// addition by our code.)
this.$myMA = new FXB.ta.EMA({period: 20});
this.$myMA.LoadData(data.valueData);
You can then copy the moving average results into a system-provided output array such as [1]:
// Copy each EMA value into output buffer #1
for (var i = 0; i < data.valueCount; i++) {
output.values[1][i] = this.$myMA.GetValue(i);
}
However, it is both simpler and faster just to take the array from the technical analysis calculation and use that to replace the pre-prepared buffer:
output.values[1] = this.$myMA.GetValueArray(); // Replaces the array, rather than writing into it
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 are based on other technical analysis calculations such as moving averages, standard deviation, or values such as ATR or RSI. (For example, an "Awesome Oscillator" is simply a 5-period moving average minus a 34-period moving average.)
UDIs have access to a library,
The
https://www.fxblue.com/figaro/scripting/help#technical-analysis
One of the features of the
In the settings for your UDI, you include a field with
// Read user-controlled maType and period fields from settings
var maType = data.parameters.maType;
var period = data.parameters.period;
// Create a moving average calculation if we don't already have one
if (!this.$myMA) this.$myMA = FXB.ta.CreateMovingAverage(maType, {period: period});
// Load the data into the calculation. If data.currentBarUpdateOnly is set then
// this automatically becomes an efficient fast update of just the current value.
this.$myMA.LoadData(data);
You can pass the data parameter from
onCalculate(data, output) {
// Create an ATR calculation if we don't already have one
if (!this.$myATR) this.$myATR = new FXB.ta.ATR({period: 14});
// Pass the data into the ATR calculation.
// If data.
this.$myATR.LoadData(data);
};
The
onCalculate(data, output) {
// If we haven't already done so in a previous call, create a candle store
if (!this.$cs) this.$cs = new FXB.CandleStore();
// Load data into the candle store. This will efficiently update just the current
// candle if data.currentBarUpdateOnly = true
this.$cs.LoadCandles(data);
// Create an ATR calculation if we haven't already done so
if (!this.$atr) this.$atr = new FXB.ta.ATR({period: 20});
// Is this a full (re-)calculation?
if (!data.currentBarUpdateOnly) {
// Full recalculation
// Load the entire candle data from the store into the ATR.
this.$atr.LoadData(this.$cs.GetCandleArray());
} else {
// Only the current bar is changing.
// Update the ATR just with the current candle from the store.
this.$atr.UpdateCurrent(this.$cs.GetCandle(0));
}
…
If there is a change to the chart, such as a switch of timeframe, or a change by the user to your user-defined settings, then the chart will always make your UDI recalculate all its data by issuing a call to onCalculate().
However, if you need to take special, extra action when these values/settings change, then you can implement the optional functions onContextChange() and onParameterChange()
For example:
onContextChange(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}
]
…
onParameterChange(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 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 onContextChange().
Objects can be created from any function in your UDI, not just in response to
onInit(data)
{
// Make a data request
var xmlr = new XMLHttpRequest();
xmlr.addEventListener("load", (xmlr) => {this.onMyDataLoad()});
xmlr.open("GET", "http://www.example.org/mydata");
xmlr.send();
// Return indicator definition
return { … };
}
onMyDataLoad(xmlt)
{
// Parse the response and create objects…
this.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
this.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
this.createDrawing([
{type: "horizontalLine", points: [{value: 1.2345}], style: {line: {color: "red"}}},
{type: "horizontalLine", points: [{value: 1.3456}], style: {line: {color: "blue"}}}
], (response) => {
// The ID of each drawing will be contained in the array response.drawingId[]
this.$myLine1 = response.drawingId[0];
this.$myLine2 = response.drawingId[1];
});
Once you have the ID of a drawing, you can delete it by passing the ID to
The properties of a drawing - text, colour, points etc - can be changed using
// Change drawing text and its colour
this.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 date and a value. For example: 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 line and/or fill and/or text sub-objects depending on the type of drawing |
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 rectangle drawing) |
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 |
freehandChannel | 4 | In effect, a filled quadrilateral with four price/time co-ordinates |
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 |
wantClick | Sends notifications when a marker is clicked on (and makes the |
You create markers by passing an individual marker definition, or an array of definitions, to
this.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
this.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"}
], (asyncResponse) => {
// The IDs of the two markers will be contained in asyncResponse.markerId[]
this.$storeMyUpMarker = asyncResponse.markerId[0];
this.$storeMyDownMarker = asyncResponse.markerId[1];
});
When you have obtained the IDs using the asynchronous callback, you can then remove an individual marker by passing its ID to
If you specify
onEventMarkerClick(marker)
{
// marker parameter contains the definition of the marker which has been clicked on
};
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
this.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
this.removeBarHighlight([data.barData.date[i - 1], data.barData.date[i - 2]]);
A UDIX has access to the full Figaro framework, and can use any of the notification mechanisms such as creating dialogs.
Without framework access, a UDI has three ways of displaying notifications to the user:
Creating toast
· Changing the panel background colour
Creating HTML (see separate section below)
A UDI can create "toast": a short-lived pop-up message to the user, displayed at the edge of the screen. You create toast using the
this.createToast({
title: "Band breakout",
text: "Price is above trend band"
});
The available options for toast are described in full in the Framework reference, but the key values are as follows:
Property | Description |
text | Body text of the notification |
title | Title for the notification. (Not compulsory.) |
icon | Hexadecimal code of an icon in the FontAwesome set, such as |
lifespan | Lifespan of the toast in seconds, overriding the app's default |
If the user clicks on the toast, then you can detect this by implementing an
onToastClick(toastDef)
{
// toastDef is the value originally passed into createToast()
}
A UDI can use
// On some condition which evaluates as an alert…
this.setBackground("red");
The parameter for
To remove a background, and revert to the app's default, use
// When the alert is cleared…
this.setBackground("transparent");
Your UDI can add HTML to its chart, either just as decoration or to provide additional functionality. This mechanism is very flexible, but it can become complicated if you want the UDI to interact with the HTML.
The HTML is added to the chart in a sandboxed iframe. It has no access to the rest of the web page, and it has no direct access to any framework or indicator features. However, the HTML can send messages to the UDI, and the UDI can send messages to the HTML. Therefore, for example, the HTML can have something like a button which sends a message to the UDI, and the UDI then carries out some action. Similarly, the HTML can have input fields. You can post a message to the UDI containing the value of the fields, and then use this data in the UDI to change its behaviour.
To display only HTML, with no standard indicator drawing, you simply return an empty plots[] array in your initialisation.
You add HTML to the chart using
this.createHTML({
foreground: true,
html: "<html><body>Hello!</body></html>"
});
A second call to
The options which are passed to
Property | Description |
html | Text of the HTML to add; a complete document with |
url | URL of a web page to load. Standard browser security requires that this is hosted at a secure https:// address. |
asDockItem | If true, loads your HTML into the chart's "dock" area (as used by the built-in Market Timer and Notepad indicators, for example). If not set, your HTML is loaded as an on-chart panel positioned using the |
foreground | Not applicable if |
style | Not applicable if style: {opacity: 0.5, pointer-events: none} You cannot set the CSS |
If you do not turn on
this.createHTML({
style: {
width: "200px",
height: "50px",
bottom: "0px",
right: "0px",
},
…
});
Your HTML can send messages to your UDI using
window.parent.postMessage("Message to UDI", "*");
This is received by the UDI in an
onHTMLMessage(msg) {
// msg == "Message to UDI"
};
Common uses include notifying the UDI of an action such as button click, or a change of value in some input field in the HTML.
Your UDI can send messages to its HTML. It is much more efficient to update the HTML by sending it a message, and by having script in the HTML which changes the contents, rather than loading complete replacement HTML using a second call to
The UDI sends a message to the HTML using
this.sendHTMLMessage("Some instruction to the HTML");
Script in the HTML listens for messages in the standard way, using a message listener on the window. For example:
window.addEventListener("message", function(event) {
// The message from the UDI will be in event.data
});
Indicators can use the
You can pass three types of parameter to
Null. Hides the icon.
An empty object,
A partial or full trading definition.
A trading definition is fundamentally the same as the parameter for SendOrder() in the Figaro framework. The major difference is that UDIs do not have access to the
this.setRecommendedTrade({
tradingAction: "SELL_LIMIT",
openPrice: 1.07,
sl: 1.08,
tp: 1.06,
comment: "My indicator"
});
Although your definition should always include a
Your definition can use all the derived properties which are accepted by
this.setRecommendedTrade({
tradingAction: "BUY",
sl: {pips: 20},
tp: 0
});
It is up to you whether you set a trading volume in your definition, or whether you omit it and leave the volume at the most recent value set by the user. If you do set a trading volume then it is most common to use a risk-based value (in combination with a stop-loss), rather than an absolute quantity. For example,
UDIs can open and populate the platform's deal ticket. It is strongly recommended that an indicator only does this in response to user action, such as clicking on an event marker or on a button in HTML you have created. Opening the deal ticket without warning is user-hostile.
You open the deal ticket using
this.createDealTicket({
message: "My suggested trade",
orderDefinition: {
tradingAction: "SELL_LIMIT",
openPrice: 1.075,
sl: {pips: 30}
}
});
The definition which you pass must contain an
The
Your definition can use all the derived properties which are accepted by
this.createDealTicket({
orderDefinition: {
tradingAction: "BUY",
sl: {pips: 20},
volume: {lots: 0.2}
}
});
Without needing to be given framework access as a UDIX, a UDI can still see account metrics and the list of open orders.
You do this by subscribing for the data, by setting
You can then define an
class MyIndicator extends UserDefinedIndicator {
onInit(data) {
return {
subscribeMetrics: true,
subscribeOrders: true,
…
}
}
onAccountData(content) {
// A change to the account metrics or order list
}
…
The parameter passed to
Member | Description of |
metrics | Account metrics such as |
orderList | Array containing an initial list of all open trades and pending orders |
orderChanges | Array containing additions, closures, and modifications of the initial order list |
If you turn on
Modifications of orders, such as the stop-loss changing, only state the order properties which have altered, rather than re-stating all the order properties. Orders which have been closed/deleted will have
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.
In addition, Figaro is hosted at an https:// address. Standard browser security will prevent you making any sort of connection to a non-secure http:// address.
The dates used on the chart depend on the user-configurable time zone. 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.timezone.offset;
var dstMode = data.context.timezone.dstMode;
The
You can convert from the user's chart time to UTC using
// Convert a value from data.barData.date[] to UTC
var currentBarDate = data.barData.date[0];
var currentAsUTC = this.convertChartDateToUTC(currentBarDate);
// Convert to Javascript date object, and output
var dateobj = new Date(currentAsUTC);
console.log("As UTC, bar date is " + dateobj.toISOString());
You can convert in the opposite direction, from UTC to chart time, using
// Get current date from computer clock (as UTC milliseconds)
var currentDateFromComputerClock = (new Date()).valueOf();
// Convert to chart time
var currentAsChartTime = this.convertUTCToChartDate(currentDateFromComputerClock);
You can also convert UTC to and from other time zones. For example, you can convert UTC to US east coast time, or vice versa.
To do this conversion, you provide a
// Base of UTC-5, moving to UTC-4 on the USA daylight savings schedule
{
offset: -300,
dstMode: UDI.DST.USA // = 1
}
You can then convert from UTC to a different zone using
var usec = this.convertUTCToZone(someDate, {offset: -300, dstMode: 1});
Similarly, you can convert from another zone to UTC using
// Convert the example above back to UTC
var utc = this.convertZoneToUTC(usec, {offset: -300, dstMode: 1});
A brief note on "multi-timeframe" (MTF) indicators.
As much as 99% of all the MTF indicators ever written are, in essence, junk. Looking back over a historic trading chart they appear to be giving impossibly good signals… because they are doing something impossible. They let themselves see into the future.
Take, for example, an indicator which compares short-term and long-term data, such as M5 and H4 candles. A typical implementation is that the indicator runs on M5 data and then additionally requests H4 bars from the environment (for example, using
Unless the indicator code is extremely careful and scrupulous, it is then going to see into the future. For example, at 10:35 yesterday on the M5 chart the indicator is going to be looking at an H4 candle with data ending at 11:59. The indicator can see into the future, knowing at 10:35 what didn't happen until 11:59.
The only safe way to create an MTF indicator is to run on the lowest timeframe and build your own candles for the higher timeframes, making sure that you do so on a cumulative, as-at basis, and don't let yourself look into the future. The FXB.CandleStore object can help with candle aggregation, but you still need to be very careful about not building a time machine for yourself.
An indicator with UDIX access can request additional bar data from the environment, using
The example UDI code contains an illustration of doing a proper MTF calculation which does not look into the future.
There is more information about remote indicators at https://www.fxblue.com/figaro/udi/remote-indicators
UDIs can be written in C# or Python instead of Javascript, but this code cannot run in a web browser. It requires a "bridge" between the browser and the C# or Python code.
There are two options:
Download and run a bridge on your own computer
Connect to the shared FX Blue bridge - higher latency, but you can then supply your indicator(s) to other users or to devices of your own other than your local computer
In older versions of the platform, indicators are/were written in a different way:
Instead of a defining a MyIndicator class, you create functions in the
Because there is no
Indicators written in this way continue to work in newer versions of the platform; it is backward-compatible. But you cannot use
You can tell whether you are using an older or newer version of the platform from the Add UDI window. Newer versions of the platform, supporting
Older versions of the platform, without Code and URL tabs in the Add UDI window, do not support the following UDI features:
Creating HTML with asDockItem turned on
Setting a recommended trade or opening the platform's deal ticket
Subscriptions for account and order data
It is easy - usually a couple of minutes' work - to convert between the old and new indicator formats.
To convert from the old format to the new format:
Define a MyIndicator class.
Copy and paste all event handler code such as
Change all remaining references to the
To convert from new format to the old format (or update your platform):
Extract all the event handlers in the
Replace all references to the class,