Figaro - User-Defined Indicators

1Introduction

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.

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.

1.1Access to the Figaro scripting framework (UDI vs UDIX)

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 list, 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

1.2Example UDI code

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 UDI.onCalculate(), it does so by storing the value in the UDI object - rather than using a global variable, for example - and it prefaces the name with $. For example, from the 04-udi-macd.js file:

UDI.$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 UDI 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 UDI properties or functions.

1.3Adding a UDI to a chart

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 Access-Control-Allow-Origin and Access-Control-Allow-Credentials on its response so that the chart is allowed to make the cross-origin HTTP request.

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).

1.4Lifecycle

The UDI is initialised once, when it is loaded. The UDI.onInit() function is not then called again if there is a change to the chart, such as a switch to a different market or timeframe, nor is it called if there is a change to the user-defined parameters. (This behaviour is different to other platforms such as MT4 and MT5.)

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()

1.5Alerts

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.

1.6Indicator-on-indicator

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.

1.7Technical note: web workers

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.

1.8Technical note: dates

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.

2Initialisation: UDI.onInit()

A UDI must have a UDI.onInit() function. This returns a description of your indicator: its name; what it draws (line, histogram etc); and any user-configurable fields it wants such as a number of bars, or a colour.

An example of the UDI.onInit() function is as follows. It returns an object which must contain a caption property, a plots[] array, and a settingsFields[] array. There are also other optional properties which can be included.

UDI.onInit = function(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}

]

};

};

2.1Return value from UDI.onInit()

The return value from UDI.onInit() should be an object, as in the example above, with the following members:

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)

plots[]

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.

settingsFields[]

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)

2.2The plots[] array

The plots[] array in the object you return from UDI.onInit() describes the different types of values which your UDI draws: a line, a histogram etc.

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 plots[] can be empty if your indicator only adds drawings, or event markers etc, to the chart.

Each plot in the array must have a caption, and a type. The type of plot determines how many values you need to pass back from UDI.onCalculate(). If you are drawing a channel plus a line, then you will need to pass back 3 arrays of values in UDI.onCalculate(): 2 for the channel plus 1 for the line.

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 caption and type. All the other properties have defaults. Some members of the plot definition only apply to particular types of plot.

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 histogramPositiveNegative or candles plots, this should be a comma-separated list of two colours, e.g. "red,#008000"

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.

2.3The settingsFields[] array

The settingsFields[] array in the object you return from UDI.onInit() describes the configurable fields which people should be able to modify for your indicator.

There is one special field which fundamentally affects the type and behaviour of your UDI. If your indicator has a Source field then it receives, in UDI.onCalculate(), a single data series such as the close prices or the high prices - or the values from another indicator. If your indicator does not have a Source field then it receives the full data for each bar - open, high, low, close, and volume - and your UDI cannot be applied to another indicator.

The Source field is defined in the settingsFields[] array as follows. (It is normal, but not compulsory, for it to be the first field.)

settingsFields: [

{id: "Source"}

]

All other fields in the settingsFields[] array should have an id, a caption, and a type. They can also optionally have other properties depending on the type of field. For example:

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

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, k, and a displayable caption, v. For example: {k: "close", v: "Close price"}. The value which your indicator receives, in UDI.onCalculate(), is the k property.

2.4Context data passed to UDI.onInit()

The UDI.onInit() function receives one parameter, data, which contains a single member, context, describing the current settings for the chart as a whole.

The same context data is also passed to all other UDI functions, including UDI.onCalculate().

The properties in data.context are as follows:

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

3Calculating data: UDI.onCalculate()

Your UDI must have a UDI.onCalculate() function. This is called whenever there is new data and, therefore, your indicator needs (or may need) to update its output values.

A simple example of the UDI.onCalculate() function is as follows:

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 = 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 UDI.onCalculate() function receives two parameters: data and output. The data tells the UDI about the chart's data values, and also about things such as the current value of the UDI's settings. The UDI then does its calculations and puts the results into output.

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 data.valueData

If your UDI does not have a Source field, then it receives the full bar data for the chart, in data.barData

3.1Reading settings fields

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 data.parameters which are passed to UDI.onCalculate().

Within data.parameters there will be a property for each settings field, using the id you defined in the settingsFields[] array, and the value of the property is the field's current value.

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"]; // Can also use data.parameters.period

}

3.2Input data for the UDI

As explained above, your UDI receives either a single array of input values, in data.valueData, or the complete bar data for the chart, in data.barData, depending on whether the UDI has a Source field.

The data.valueData is a single array of input values, such as close prices or the values from another indicator. If your UDI is receiving valueData, then it also has access to the date for each value/point, via a data.dates[] array.

The data.barData contains sub-arrays for date, open, high, low, close, and volume: data.barData.date, data.barData.open, data.barData.high , data.barData.low, data.barData.close, and data.barData.volume.

The data.valueCount property defines the number of input data points. It will be same as the length of the data.valueData array, or of each array inside data.barData.

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 data.dates or data.barDate.date, are values in milliseconds reflecting the user-configurable time zone for the chart.

3.3Updates of the current bar only

To make calculations faster and more efficient, the chart tells your UDI whether only the current bar has changed since the last calculation, in the data.currentBarUpdateOnly property.

Typically, if data.currentBarUpdateOnly is set, then you only need to re-calculate the final, most recent item in your output. If it is not set, then you need to re-calculate the whole series of output values from your indicator, because any part of the historic data may have changed.

The example code contains two versions of an SMA indicator: one which always re-calculates all values, and a more efficient one which uses data.currentBarUpdateOnly to update only the current average value if only the current bar is changing.

3.4Calculating output data

You put the results of your UDI's calculations into the output object which is passed to your UDI.onCalculate().

The output has a values member which is an array of arrays. The length, and order, of output.values depends on the plots defined for your indicator. For example:

plots: [

{type: "channel", …}, // Requires 2 output series

{type: "line", …}, // Requires 1 output series

{type: "candles", …} // Requires 4 output series

]

In this example, output.values will contain 7 sub-arrays. Items [0] and [1] will be for the channel plot; item [2] will be for the line plot; and items [3] to [6] will be for the candle plot.

Each sub-array inside output.values will already have been initialised so that it contains the required number of elements (matching the number of bars on the chart). In other words, the length of each sub-array, such as output.values[0].length, will be the same as data.valueCount. (Therefore, you assign values into the sub-arrays rather than using push() to add data.)

If UDI.onCalculate() is being called for an update of the current bar only, with data.currentBarUpdateOnly set, then the contents of output.values will be the last results you passed back. Typically, you only then need to re-calculate the latest value in each sub-array. For example (not fully efficient!):

UDI.onCalculate = function(data, output)

{

// Get the user-selected value of the "period" parameter

var period = data.parameters["period"];

if (!data.currentBarUpdateOnly) {

// Need to do a full recalculation (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 output.values, it's permissible for you to replace them with new arrays instead of writing your values into the existing arrays. This is typically most relevant when working with technical analysis calculations. For example, let's say that you have used the FXB.ta library to calculate a moving average of the input values:

// 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.)

UDI.$myMA = new FXB.ta.EMA({period: 20});

UDI.$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] = UDI.$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] = UDI.$myMA.GetValueArray(); // Replaces the array, rather than writing into it

3.5Requesting out-of-band updates

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 UDI.requestUpdate() to force an immediate recalculation of your indicator. This will generate an asynchronous but immediate call to UDI.onCalculate().

3.6Technical analysis: moving averages, ATR etc

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, FXB.ta, of technical analysis calculations. Two of the example UDI files use this library, to calculate a moving average and to build up multiple averages into a MACD calculation.

The FXB.ta library is documented separately, because it is also available as part of the Figaro scripting framework. UDIs also have access to an FXB.CandleStore helper. You can read about these resources at the following address:

https://www.fxblue.com/figaro/scripting/help#technical-analysis

One of the features of the FXB.ta library is creating a configurable type of moving average from a user-controlled setting in your UDI. This is illustrated by the MACD example file, and works as follows:

In the settings for your UDI, you include a field with type:"maType". In your UDI code, you read the value of the user field (and typically also of a user-defined period for the average), and use FXB.ta.CreateMovingAverage() to create the moving average object. For example:

// 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 (!UDI.$myMA) UDI.$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.

UDI.$myMA.LoadData(data);

You can pass the data parameter from UDI.onCalculate() directly into the LoadData() of a technical analysis calculation - for both "bar-based" and "value-based" calculations. The LoadData() function automatically checks currentBarUpdateOnly and does an efficient update of only the current value if only the current bar is changing. This is demonstrated by the example above, and also the following instance of a bar-based calculation (ATR):

UDI.onCalculate = function(data, output) {

// Create an ATR calculation if we don't already have one

if (!UDI.$myATR) UDI.$myATR = new FXB.ta.ATR({period: 14});

// Pass the data into the ATR calculation.

// If data.currentBarUpdateOnly is set then the calculation does a fast call to UpdateCurrent()

// rather than a full reload of all the data

UDI.$myATR.LoadData(data);

};

The FXB.CandleStore can also be helpful here. You can pass the data parameter from UDI.onCalculate() into a candle store, and it will convert it into an array of candle objects. That array can then be sent into a bar-based technical analysis calculation such as ATR. For example:

UDI.onCalculate = function(data, output) {

// If we haven't already done so in a previous call, create a candle store

if (!UDI.$cs) UDI.$cs = new FXB.CandleStore();

// Load data into the candle store. This will efficiently update just the current

// candle if data.currentBarUpdateOnly = true

UDI.$cs.LoadCandles(data);

// Create an ATR calculation if we haven't already done so

if (!UDI.$atr) UDI.$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.

UDI.$atr.LoadData(UDI.$cs.GetCandleArray());

} else {

// Only the current bar is changing.

// Update the ATR just with the current candle from the store.

UDI.$atr.UpdateCurrent(UDI.$cs.GetCandle(0));

}

4Changes to the chart or parameters

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 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()

4.1UDI.onContextChange()

UDI.onContextChange() is called whenever there is a change to the chart such as a switch of market or timeframe. The function receives a data parameter which contains the same context information which is passed to UDI.onInit().

For example:

UDI.onContextChange = function(data)

{

// Get current chart timeframe

var tf = data.context.instrument.timeframe;

};

4.2UDI.onParameterChange()

UDI.onParameterChange() is called whenever there is a change to the user-defined settings for your UDI. The function receives a data parameter which contains the new settings, as a parameters object. It also receives the same context information which is passed to UDI.onInit(), in data.context.

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"];

}

5Creating drawings, event markers, and bar highlights

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 UDI.onInit(). If you want to create objects on start-up, do so from the first call to UDI.onCalculate() which is always issued immediately after an indicator is loaded.

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 UDI.onCalculate().

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.onCalculate(). For example, you can use XMLHttpRequest to get external data, and then display that data on the chart as drawings:

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.

5.1Creating drawings

You create drawings using UDI.createDrawing(). The parameter for this function is either a single drawing definition, or an array of definitions. For example:

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 UDI.removeAllDrawings().

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(). For example:

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 UDI.removeDrawing().

A drawing can be moved by passing an object containing the ID and a new array of points to UDI.moveDrawing(). For example:

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 UDI.changeDrawing(). You pass an object containing the ID and any properties you want to change. For example:

// Change drawing text and its colour

UDI.changeDrawing({

drawingId: …,

text: "New text",

style: {text: {color: "red"}}

});

UDI.removeDrawing(), UDI.moveDrawing(), and UDI.changeDrawing() can be given an array as their parameter, rather than a single item.

5.1.1Drawing properties

Drawings can have the following properties. Each drawing must have a type and an array of points[]. The number of required points depends on the type of drawing: one for a horizontal line, two for a rectangle etc

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:
points: [{date: data.barData.date[0]}, value: data.barData.high[0]}]

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:
points: [{x:100, y:100}, {x:200, y:200}]

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 color, width, and lineStyle. The style of the line can be "solid", "dash", or "dot". For some types of drawing, such as lines, the style 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".

style.fill

Can contain a color property, specifying fill color

style.text

Can contain the properties color, fontFamily, fontSize, and fontStyle

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 barMarker drawing type, offsets the position of the marker from the price by the specified number of pixels (negative for above, positive for below)

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

5.1.2Types of drawing

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 lineSegment, but extends the line from the second price to the end of the chart

diagonal

2

Like lineSegment, but extends the line to both chart edges

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)

5.2Creating event markers

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 text setting redundant)

You create markers by passing an individual marker definition, or an array of definitions, to UDI.createEventMarker(). If you are creating several markers at once, it is far more efficient to pass an array than to make multiple calls to UDI.createEventMarker(). For example:

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 UDI.removeAllEventMarkers().

You can also remove individual markers (rather than removing everything and re-creating different ones). The UDI.createEventMarker() function can be given an optional callback function which receives, asynchronously, an array of the markers which have been created. For example:

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 UDI.removeEventMarker().

5.2.1Click notifications from event markers

If you specify wantClick:true in the marker configuration, then the text setting becomes redundant and is not used, and the chart instead sends a message to the UDI when a marker is clicked on. You receive these notifications using a UDI.onEventMarkerClick() function:

UDI.onEventMarkerClick = function(marker)

{

// marker parameter contains the definition of the marker which has been clicked on

};

5.3Creating bar highlights

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 (). If you are creating several highlights at once, it is far more efficient to pass an array than to make multiple calls to UDI.createBarHighlight (). For example:

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 UDI.removeAllBarHighlights().

Individual highlights can be removed by passing a date, or an array of dates, to UDI.removeBarHighlight(). For example:

UDI.removeBarHighlight([data.barData.date[i - 1], data.barData.date[i - 2]]);

6Notifying the user

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)

6.1Creating toast

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 UDI.createToast() function. This takes two parameters: a definition of the toast to display, and an optional callback function which receives a notification if the user clicks on the toast. For example:

UDI.createToast({

title: "Band breakout",

text: "Price is above trend band"

}, function(toastDef) {

// User has clicked on the toast

// toastDef parameter is the definition passed to createToast

});

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 "f05a" for an info icon. (Not compulsory.)

lifespan

Lifespan of the toast in seconds, overriding the app's default

6.2Changing the panel background colour

A UDI can use UDI.setBackground() to change the background colour of its panel, typically as a way of notifying the user of an alert condition. For example:

// On some condition which evaluates as an alert…

UDI.setBackground("red");

The parameter for UDI.setBackground() can be any valid CSS background colour or style; it is directly setting the style.background of the HTML element which contains the indicator.

To remove a background, and revert to the app's default, you simply pass an empty string:

// When the alert is cleared…

UDI.setBackground("");

7Adding HTML to the chart

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.

7.1Adding HTML

You add HTML to the chart using UDI.createHTML(). For example:

UDI.createHTML({

foreground: true,

html: "<html><body>Hello!</body></html>"

});

A second call to UDI.createHTML() replaces the previous HTML with the new HTML. You can remove your HTML from the chart using UDI.removeHTML().

The options which are passed to UDI.createHTML() can contain the following properties. You must provide either the html or url.

Property

Description

html

Text of the HTML to add; a complete document with <html> and </html> tags

url

URL of a web page to load. Standard browser security requires that this is hosted at a secure https:// address.

foreground

Controls whether the HTML is displayed in the background, behind the indicator drawing, or in the foreground. If your HTML includes elements for the user to interact with, such as buttons or fields, then you must set foreground:true.

style

Lets you set CSS style options on the iframe container, particularly the position. For example:

style: {opacity: 0.5, pointer-events: none}

You cannot set the position, which is always absolute (within its container). You cannot set zIndex, which is instead controlled by the foreground option.

7.2Positioning the HTML

By default, the iframe and your HTML occupy the whole of your indicator's panel: width:100%, height:100%. You can use the style settings to change this. For example, making the HTML 200px by 50px, positioned in the bottom right corner of the panel:

UDI.createHTML({

style: {

width: "200px",

height: "50px",

bottom: "0px",

right: "0px",

},

});

7.3Sending messages from the HTML to the UDI

Your HTML can send messages to your UDI using window.parent.postMessage(). For example:

window.parent.postMessage("Message to UDI", "*");

This is received by the UDI in a UDI.onHTMLMessage() function. For example:

UDI.onHTMLMessage = function(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.

7.4Sending messages from the UDI to 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 UDI.createHTML().

The UDI sends a message to the HTML using UDI.sendHTMLMessage(). For example:

UDI.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

});

8Recommended trade

Indicators can use the UDI.setRecommendedTrade() function to set a "recommended trade", signalled to the user by a lightning-bolt icon next to the name of the indicator on the chart. Clicking on the icon then fills the recommendation into the chart's deal ticket, ready for placement.

Note: this functionality is not available in older versions of the platform. To avoid run-time errors, you should check for its availability before trying to use it:

if (UDI.setRecommendedTrade) {

// Function(ality) is available…

}

You can pass three types of parameter to UDI.setRecommendedTrade():

Null. Hides the icon.

An empty object, {}. Icon is visible, but disabled and greyed-out.

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 FXB.OrderTypes enumeration. You specify the order type using the textual name from the enumeration, such as "SELL_LIMIT", rather than a constant such as FXB.OrderTypes.SELL_LIMIT. For example:

UDI.setRecommendedTrade({

tradingAction: "SELL_LIMIT",

openPrice: 1.07,

sl: 1.08,

tp: 1.06,

comment: "My indicator"

});

Although your definition should always include a tradingAction, all the properties of the definition are optional rather than compulsory. The deal ticket will only update with the properties which you include. For example, if you do not include a tp then the take-profit field in the deal ticket will be unchanged, and will continue to use/show any previous value set by the user. Therefore, if you specifically want to recommend an order with no take-profit, you should set tp:0 rather than omitting the tp property.

Your definition can use all the derived properties which are accepted by SendOrder(). For example, you can use the following to set the stop-loss at 20 pips from the entry price, varying tick by tick while the deal ticket is open:

UDI.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, volume: {equityPercent: 1}.

9.1Collecting external data

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, XMLHttpRequest is subject to browser security: your web browser will only let you make a cross-origin call to a remote server if that server permits such a call by setting the CORS headers Access-Control-Allow-Origin and Access-Control-Allow-Credentials on its response.

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.

9.2Converting dates to chart time

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 data parameter for all functions. For example:

// Get base offset in minutes, and daylight savings mode

var offsetMinutes = data.context.timezone.offset;

var dstMode = data.context.timezone.dstMode;

The dstMode is one of the values from the UDI.DST enumeration: 0 = no daylight savings, 1 = USA schedule, 2 = European schedule, 3 = Australian schedule

You can convert from the user's chart time to UTC using UDI.convertChartDateToUTC(). This returns an adjusted date as a number of milliseconds, which you can then convert to a Date() object if necessary. For example:

// Convert a value from data.barData.date[] to UTC

var currentBarDate = data.barData.date[0];

var currentAsUTC = UDI.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 UDI.convertUTCToChartDate(). For example:

// Get current date from computer clock (as UTC milliseconds)

var currentDateFromComputerClock = (new Date()).valueOf();

// Convert to chart time

var currentAsChartTime = UDI.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 timezone structure, like the one which is included in the UDI's context data. This consists of an offset, in minutes, and a dstMode. For example, the definition of New York time is:

// 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 UDI.convertUTCToZone(). For example, converting a date from UTC to US east coast. What this will do is to return the UTC number of milliseconds minus either 300 minutes or 240 minutes depending on whether the USA was in daylight savings at the date you supply:

var usec = UDI.convertUTCToZone(someDate, {offset: -300, dstMode: 1});

Similarly, you can convert from another zone to UTC using UDI.convertZoneToUTC(). This adds either 300 minutes or 240 minutes to the number of milliseconds you supply. For example:

// Convert the example above back to UTC

var utc = UDI.convertZoneToUTC(usec, {offset: -300, dstMode: 1});

9.3Multi-timeframe (MTF) indicators

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 iBarShift etc in MT4).

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 Framework.RequestCandles(). Doing this, however, for a higher timeframe will almost certainly lead to a junk indicator.

The example UDI code contains an illustration of doing a proper MTF calculation which does not look into the future.