First Simple Indicator (GMA)

From FxCodeBaseWiki
Jump to: navigation, search

The Specification

The Guppy Moving Average Indicator shows 12 exponential moving average lines: 6 short (3, 5, 8, 10, 12, 15) and 6 long (30, 35, 40, 45, 50, 60). The short lines must be blue and the long lines must be green.

Write It

Create File

First create the empty file:

Once the new indicator file is created, you can save it. The file name is an acronym or a shortened name for the indicator (such as MVA for simple moving average). You can now call your indicator GMA (for Guppy Multiple Moving Average).

  • Click the File->Save As command. Since you created an indicator, the editor opens the indicators folder of SDK. If it does not, please change the folder. Sometimes managing the File Open/Save dialog which is changed in every new Windows version, is not successful.
  • Enter GMA.lua as the file name and then click on Save.

That's all. The file is created.

"Introduce" Indicator

All information regarding “introduce” must be specified in the Init function of the indicator. So, let’s write it. First, define a “human-friendly” name.

function Init()
    indicator:name("Guppy's Multiple Moving Average");
end

Next, you must tell the Marketscope what is the price that you can apply the indicator to. In your case, the indicator does not use open, high, low and close prices at the same time and it can be applied to any of these prices. For example, it can be applied to close prices, only. Also, the indicator can be applied to tick data. So, the prices required for this indicator are tick prices.

function Init()
    indicator:name("Guppy's Multiple Moving Average");
    indicator:requiredSource(core.Tick);
end

Then, you must indicate how to place your indicator on the chart. Your indicator processes the data that is similar to the source prices, so it can be drawn over these prices. However, users can specify that the indicator must be drawn in a separate area but by default, the Marketscope recommends that the indicator is drawn over the prices.

function Init()
    indicator:name("Guppy's Multiple Moving Average");
    indicator:requiredSource(core.Tick);
    indicator:type(core.Indicator);
end

That’s all. Marketscope now knows everything necessary to let the user apply your indicator to the chart.

Define Indicator Behavior

The next step is to describe what to do when the user has already applied the indicator to the price chart.

Applying Indicator on Chart

The Prepare function is called every time the user changes the instrument (such as applying the indicator) or the period of the chart. At this point, you must state what you want to draw in Marketscope and prepare your code for the calculation of values in the future.

Let’s start. First, add the Prepare function and put the price data into the global variable for easier access in future. The code for the Prepare function is shown below:

function Prepare(onlyName)
    local name;

    -- set the indicator name 
    name = profile:id() .. "(" .. instance.source:name() .. ")";
    instance:name(name);

    -- if indicator is called to update the name only - do nothing else
    if onlyName then
        return ;
    end
end


Then, you have to create twelve lines which show your EMA’s indicators. You have to remember the tables returned by the method (addStream) which is used for creating the indicator lines. These tables are used for filling the calculated data. Please, pay attention to the last parameter of the addStream method. This is the first period which can be calculated for the line. In your case, you are going to use exponential moving average, so that your data may be calculated as soon as the source data is available.

You may ask how could it happen that the source data is not available at 0 (the oldest) bar? It's simple. If the source is a price, it cannot. Your indicator, however, is a tick indicator, and as such, it can be applied to other indicators which may have a lag against the first bar of the price.

You store all of your EMAs into the array. Introduce the CreateEMA function to avoid duplicating the same code. Also, at the same time, you may store the source, the first bar and calculate the value of the formula. None of this data is changed without having the indicator closed and applied again. Storing this data makes future execution faster.

local source = nil; -- the source
local first;        -- the oldest source data available
local EMAs = {};    -- an array of outputs

function CreateEMA(index, N, color, name)
    local label, ema;
    ema = {};
    -- line label
    label = "EMA" .. N;
    -- create the line
    ema.alpha = 2.0 / (N + 1.0);
    ema.stream = instance:addStream(label, core.Line, name .. label, label, color, source:first());

    EMAs[index] = ema;
end

function Prepare(onlyName)
    local name;

    -- set the indicator name (use the short name of your indicator: GMMA)
    name = profile:id() .. "(" .. instance.source:name() .. ")";
    instance:name(name);

    -- if indicator is called to update the name only - do nothing else
    if onlyName then
        return ;
    end

    source = instance.source;
    first = source:first();

    CreateEMA(0, 3, core.rgb(0, 0, 255), name);
    CreateEMA(1, 5, core.rgb(0, 0, 255), name);
    CreateEMA(2, 8, core.rgb(0, 0, 255), name);
    CreateEMA(3, 10, core.rgb(0, 0, 255), name);
    CreateEMA(4, 12, core.rgb(0, 0, 255), name);
    CreateEMA(5, 15, core.rgb(0, 0, 255), name);

    CreateEMA(6, 30, core.rgb(255, 0, 0), name);
    CreateEMA(7, 35, core.rgb(255, 0, 0), name);
    CreateEMA(8, 40, core.rgb(255, 0, 0), name);
    CreateEMA(9, 45, core.rgb(255, 0, 0), name);
    CreateEMA(10, 50, core.rgb(255, 0, 0), name);
    CreateEMA(11, 60, core.rgb(255, 0, 0), name);
end

Calculating Indicator

That’s all. Your indicator explained what to draw and is ready to calculate the lines. Now, you can write the most important part of the indicator: the Update function.

This function is called for each period, starting from the oldest one which is not calculated yet. So, the first time the Update function is called for each period of the source price. Then, it is called only for newly appearing periods.

At first, write the function which calculates the EMA value for the particular period. There are three cases:

  1. If the period is before the first period which can be calculated, then do nothing.
  2. If it’s the first period you can calculate, then just take the price value.
  3. If it’s the next period, then calculate the EMA for the specified period.
function CalcEMA(index, period)
    local ema = EMAs[index];
    if period < first then
       return ;
    elseif period == first then
       ema.stream[period] = source[period];
    else
       ema.stream[period] = source[period] * ema.alpha + ema.stream[period - 1] * (1 - ema.alpha);
    end
end


Are you tired yet? The final step is to write the Update function itself and call the calculation function for each of your twelve lines for the indicator.

function Update(period)
    CalcEMA(0, period);
    CalcEMA(1, period);
    CalcEMA(2, period);
    CalcEMA(3, period);
    CalcEMA(4, period);
    CalcEMA(5, period);

    CalcEMA(6, period);
    CalcEMA(7, period);
    CalcEMA(8, period);
    CalcEMA(9, period);
    CalcEMA(10, period);
    CalcEMA(11, period);
end

Final Version

If everything is done correctly, then you have the indicator typed in the Integrated Lua EditorDebugger. At this point, save it.

You can also download the final version. Just right-click on the name GMA.lua and choose "Save Target As...". Save the GMA.lua file to the indicators subfolder of the Indicore SDK folder.

'"`UNIQ--toggledisplay-0000000E-QINU`"'

Try It

If everything is done correctly, the indicator is located in the proper place and is ready to be debugged.

  • Run Integrated Lua EditorDebugger.
  • Choose File->Open Indicator and then choose GMA.lua in the list of the indicators.
  • When the indicator starts, run it (click Debug->Run).
  • The indicator properties appear. Choose the price source to simulate the prices. To do so, select the "Price Source" row and click on ... button to choose the source. You should see the Choose Price Source dialog box:
    Luadebugging6-1-2016.png

    You can choose the file with prices or use data from the quotes manager server:
    • To choose the price file, click on File and then click the ellipsis (...) button. You can refer to the CodeBase Price Archive to get data files for 1-year.
    • You can use the quotes manager server to download the 1-minute quote data for the chosen instrument in chosen period. The data is downloaded rapidly. Loading of the entire year of 1-minute data for an instrument (approx 300K candles) usually takes less than 30 seconds. To use the data from the quotes manager, click on Quotes Manager and then choose instrument, time frame and data range.
      After configuring the parameters, click on OK. If you have chosen to load the data from the quotes manager server and you had not downloaded the data earlier, you should see the message box asking whether you want to download the data now. Click on Yes. You should see the Load Quotes dialog box:
      Luadebugging6-1-2016.png
      Click on OK to start downloading the data for the chosen instrument and period. Wait a bit while the data is loaded
      Luadebugging6-3.PNG
  • After the price source is chosen, click on OK in the Parameters dialog box, to continue.
  • The debugger works for a few seconds and then "The update is finished" message appears in the Output log.
  • Switch to the Chart tab and then click Debug->Chart->Fit All Bars, to see the entire image.
Gma2.png

That's all.

Improve It

Use the Standard EMA Implementation

Actually, the EMA already exists in Marketscope, so we don't need to write it. All we need to do is use the existing indicator. And now, use it.

First, we must apply EMA indicator on the source data with all the parameters that we have. Do this in the CreateEMA() function:

  • We don't need to calculate the alpha value anymore. Let EMA indicator care about that.
  • Although, we know that EMA could be displayed from the oldest of the source price periods. Make your code reliable and ask the indicator what is the oldest period it can calculate. If in future this value is changed, you won't need to update your indicator.
function CreateEMA(index, N, color, name)
    local label, ema;
    ema = {};
    -- line label
    label = "EMA" .. N;
    -- create the EMA indicator
    ema.indicator = core.indicators:create("EMA", source, N);
    -- create get the oldest period it can calculate
    ema.first = ema.indicator.DATA:first();
    -- and create your line to draw
    ema.stream = instance:addStream(label, core.Line, name .. label, label, color, ema.first);

    EMAs[index] = ema;
end


Now, change the calculation process. Instead of calculating the value by yourself, you just need to force indicators to be updated and get their values then. Please note that another parameter is added to the Update function. It is almost useless for the simple indicators but is used to optimize the update of all the indicators created in your indicators. So, just pass this parameter to the bunch of EMAs that we have created.

function CalcEMA(index, period, mode)
    local ema = EMAs[index];
    -- force EMA to be updated
    ema.indicator:update(mode);
    
    -- and fill your output if it has data.
    if period < ema.first then
       return ;
    else
       ema.stream[period] = ema.indicator.DATA[period];
    end
end

function Update(period, mode)
    CalcEMA(0, period, mode);
    CalcEMA(1, period, mode);
    CalcEMA(2, period, mode);
    CalcEMA(3, period, mode);
    CalcEMA(4, period, mode);
    CalcEMA(5, period, mode);

    CalcEMA(6, period, mode);
    CalcEMA(7, period, mode);
    CalcEMA(8, period, mode);
    CalcEMA(9, period, mode);
    CalcEMA(10, period, mode);
    CalcEMA(11, period, mode);
end

Voila! Now, we don't calculate EMA anymore. Instead, we use the Standard EMA indicator.

Customize Lines Parameters and Colors

Now, make your indicator customizable. The user must be able to choose the parameters for EMA calculation and for the display of lines. To add the parameters, you must return to the Init() function.

The parameter is any (integer, real, boolean, string, etc.) value which can be entered by the user when the indicator is about to be applied on the chart. To help the Marketscope ask for these parameters, you must also "introduce" them as you have introduced the indicator.

For each parameter you must state the:

  • Identifier, acronym or short name that you want to use to request the value.
  • "Human-friendly" name which is shown to the user.
  • Default value which is used if the user does not enter any value.
  • Minimum and maximum values (used for numeric parameters).

Now, you have twelve lines and you must specify the parameters for each of them.

First, add the numeric parameters. The EMA gets an integer positive parameter for the number of bars to be calculated.

function Init()
    indicator:name("Guppy's Multiple Moving Average");
    indicator:requiredSource(core.Tick);
    indicator:type(core.Indicator);

    indicator.parameters:addInteger("S1", "1st Short Line", "", 3, 2, 10000);
    indicator.parameters:addInteger("S2", "2nd Short Line", "", 5, 2, 10000);
    indicator.parameters:addInteger("S3", "3rd Short Line", "", 8, 2, 10000);
    indicator.parameters:addInteger("S4", "4th Short Line", "", 10, 2, 10000);
    indicator.parameters:addInteger("S5", "5th Short Line", "", 12, 2, 10000);
    indicator.parameters:addInteger("S6", "6th Short Line", "", 15, 2, 10000);
    indicator.parameters:addInteger("L1", "1st Long Line", "", 30, 2, 10000);
    indicator.parameters:addInteger("L2", "2nd Long Line", "", 35, 2, 10000);
    indicator.parameters:addInteger("L3", "3rd Long Line", "", 40, 2, 10000);
    indicator.parameters:addInteger("L4", "4th Long Line", "", 45, 2, 10000);
    indicator.parameters:addInteger("L5", "5th Long Line", "", 50, 2, 10000);
    indicator.parameters:addInteger("L6", "6th Long Line", "", 60, 2, 10000);
end

These parameters are now available as instance.parameters.ID. An example of this is the, instance.parameters.S1, for the value entered on the first short line.

Now, replace the constants in the Prepare function with the parameter values:

function Prepare(onlyName)
    local name;

    -- set the indicator name (use the short name of your indicator: GMMA)
    name = profile:id() .. "(" .. instance.source:name() .. ")";
    instance:name(name);

    -- if indicator is called to update the name only - do nothing else
    if onlyName then
        return ;
    end

    source = instance.source;
    first = source:first();

    CreateEMA(0, instance.parameters.S1, core.rgb(0, 0, 255), name);
    CreateEMA(1, instance.parameters.S2, core.rgb(0, 0, 255), name);
    CreateEMA(2, instance.parameters.S3, core.rgb(0, 0, 255), name);
    CreateEMA(3, instance.parameters.S4, core.rgb(0, 0, 255), name);
    CreateEMA(4, instance.parameters.S5, core.rgb(0, 0, 255), name);
    CreateEMA(5, instance.parameters.S6, core.rgb(0, 0, 255), name);

    CreateEMA(6, instance.parameters.L1, core.rgb(255, 0, 0), name);
    CreateEMA(7, instance.parameters.L2, core.rgb(255, 0, 0), name);
    CreateEMA(8, instance.parameters.L3, core.rgb(255, 0, 0), name);
    CreateEMA(9, instance.parameters.L4, core.rgb(255, 0, 0), name);
    CreateEMA(10, instance.parameters.L5, core.rgb(255, 0, 0), name);
    CreateEMA(11, instance.parameters.L6, core.rgb(255, 0, 0), name);
end

Next, make the colors and line style configurable, as well. You need a couple of parameters for colors (for short and long), for the line width and for line style. Although, the line style parameter is also just an integer, a flag is used to tell Marketscope that you need a choice for the line styles, here.

function Init()
    indicator:name("Guppy's Multiple Moving Average");
    indicator:requiredSource(core.Tick);
    indicator:type(core.Indicator);

    indicator.parameters:addInteger("S1", "1st Short Line", "", 3, 2, 10000);
    indicator.parameters:addInteger("S2", "2nd Short Line", "", 5, 2, 10000);
    indicator.parameters:addInteger("S3", "3rd Short Line", "", 8, 2, 10000);
    indicator.parameters:addInteger("S4", "4th Short Line", "", 10, 2, 10000);
    indicator.parameters:addInteger("S5", "5th Short Line", "", 12, 2, 10000);
    indicator.parameters:addInteger("S6", "6th Short Line", "", 15, 2, 10000);
    indicator.parameters:addInteger("L1", "1st Long Line", "", 30, 2, 10000);
    indicator.parameters:addInteger("L2", "2nd Long Line", "", 35, 2, 10000);
    indicator.parameters:addInteger("L3", "3rd Long Line", "", 40, 2, 10000);
    indicator.parameters:addInteger("L4", "4th Long Line", "", 45, 2, 10000);
    indicator.parameters:addInteger("L5", "5th Long Line", "", 50, 2, 10000);
    indicator.parameters:addInteger("L6", "6th Long Line", "", 60, 2, 10000);

    indicator.parameters:addColor("SC", "Short Line Color", "", core.rgb(0, 0, 255));
    indicator.parameters:addColor("LC", "Long Line Color", "", core.rgb(255, 0, 0));
    indicator.parameters:addInteger("W", "Line Width", "", 1, 1, 5);
    indicator.parameters:addInteger("S", "Line Style", "", core.LINE_SOLID);
    indicator.parameters:setFlag("S", core.FLAG_LEVEL_STYLE);
end

local source = nil; -- the source
local first;        -- the oldest source data available
local EMAs = {};    -- an array of outputs

function CreateEMA(index, N, color, name)
    local label, ema;
    ema = {};
    -- line label
    label = "EMA" .. N;
    -- create the line
    ema.indicator = core.indicators:create("EMA", source, N);
    ema.first = ema.indicator.DATA:first();
    ema.stream = instance:addStream(label, core.Line, name .. label, label, color, ema.first);
    ema.stream:setWidth(instance.parameters.W);
    ema.stream:setStyle(instance.parameters.S);

    EMAs[index] = ema;
end

function Prepare(onlyName)
    local name;

    -- set the indicator name (use the short name of your indicator: GMMA)
    name = profile:id() .. "(" .. instance.source:name() .. ")";
    instance:name(name);

    -- if indicator is called to update the name only - do nothing else
    if onlyName then
        return ;
    end

    source = instance.source;
    first = source:first();

    CreateEMA(0, instance.parameters.S1, instance.parameters.SC, name);
    CreateEMA(1, instance.parameters.S2, instance.parameters.SC, name);
    CreateEMA(2, instance.parameters.S3, instance.parameters.SC, name);
    CreateEMA(3, instance.parameters.S4, instance.parameters.SC, name);
    CreateEMA(4, instance.parameters.S5, instance.parameters.SC, name);
    CreateEMA(5, instance.parameters.S6, instance.parameters.SC, name);

    CreateEMA(6, instance.parameters.L1, instance.parameters.LC, name);
    CreateEMA(7, instance.parameters.L2, instance.parameters.LC, name);
    CreateEMA(8, instance.parameters.L3, instance.parameters.LC, name);
    CreateEMA(9, instance.parameters.L4, instance.parameters.LC, name);
    CreateEMA(10, instance.parameters.L5, instance.parameters.LC, name);
    CreateEMA(11, instance.parameters.L6, instance.parameters.LC, name);
end

That's all. However, at this point, you may need to refine the list of parameters to make it more navigable. You can split it into groups:

function Init()
    indicator:name("Guppy's Multiple Moving Average");
    indicator:requiredSource(core.Tick);
    indicator:type(core.Indicator);

    indicator.parameters:addGroup("Short Lines");
    indicator.parameters:addInteger("S1", "1st Line", "", 3, 2, 10000);
    indicator.parameters:addInteger("S2", "2nd Line", "", 5, 2, 10000);
    indicator.parameters:addInteger("S3", "3rd Line", "", 8, 2, 10000);
    indicator.parameters:addInteger("S4", "4th Line", "", 10, 2, 10000);
    indicator.parameters:addInteger("S5", "5th Line", "", 12, 2, 10000);
    indicator.parameters:addInteger("S6", "6th Line", "", 15, 2, 10000);
    indicator.parameters:addGroup("Long Lines");
    indicator.parameters:addInteger("L1", "1st Line", "", 30, 2, 10000);
    indicator.parameters:addInteger("L2", "2nd Line", "", 35, 2, 10000);
    indicator.parameters:addInteger("L3", "3rd Line", "", 40, 2, 10000);
    indicator.parameters:addInteger("L4", "4th Line", "", 45, 2, 10000);
    indicator.parameters:addInteger("L5", "5th Line", "", 50, 2, 10000);
    indicator.parameters:addInteger("L6", "6th Line", "", 60, 2, 10000);
    indicator.parameters:addGroup("Style");
    indicator.parameters:addColor("SC", "Short Line Color", "", core.rgb(0, 0, 255));
    indicator.parameters:addColor("LC", "Long Line Color", "", core.rgb(255, 0, 0));
    indicator.parameters:addInteger("W", "Line Width", "", 1, 1, 5);
    indicator.parameters:addInteger("S", "Line Style", "", core.LINE_SOLID);
    indicator.parameters:setFlag("S", core.FLAG_LEVEL_STYLE);
end

Make Smoothing Method Customizable

Finally, make the smoothing method customizable, as well. The identifier of the indicator is just a string, so all that we need to do is add a string parameter and then replace the "EMA" string constant with the parameter.

function Init()
    indicator:name("Guppy's Multiple Moving Average");
    indicator:requiredSource(core.Tick);
    indicator:type(core.Indicator);

    indicator.parameters:addGroup("Method");
    indicator.parameters:addString("M", "Smoothing Method", "", "EMA");

    ...
end

function CreateEMA(index, N, color, name)
    local label, ema;
    ema = {};
    -- line label
    label = instance.parameters.M .. N;
    -- create the line
    ema.indicator = core.indicators:create(instance.parameters.M, source, N);
    ema.first = ema.indicator.DATA:first();
    ema.stream = instance:addStream(label, core.Line, name .. label, label, color, ema.first);
    ema.stream:setWidth(instance.parameters.W);
    ema.stream:setStyle(instance.parameters.S);

    EMAs[index] = ema;
end

Now, if the user enters MVA instead of EMA, the simple moving average is then used instead of the exponential moving average. However, it is not a good idea to require the user to remember all of these names. Make this parameter a choice.

function Init()
    indicator:name("Guppy's Multiple Moving Average");
    indicator:requiredSource(core.Tick);
    indicator:type(core.Indicator);

    indicator.parameters:addGroup("Method");
    indicator.parameters:addString("M", "Smoothing Method", "", "EMA");
    indicator.parameters:addStringAlternative("M", "Simple Moving Average", "", "MVA");
    indicator.parameters:addStringAlternative("M", "Linear Weighted Moving Average", "", "LWMA");
    indicator.parameters:addStringAlternative("M", "Exponential Moving Average", "", "EMA");
    indicator.parameters:addStringAlternative("M", "Smoothed Moving Average", "", "SMMA");
    indicator.parameters:addStringAlternative("M", "Wilders Moving Average", "", "WMA");
    indicator.parameters:addGroup("Short Lines");
    ...
end

Final Version

You can download the final version with all of the improvements you have made, here. Just right-click on the name GMA1.lua and choose "Save Target As...". Save the GMA1.lua file to the indicators subfolder of the Indicore SDK folder.

'"`UNIQ--toggledisplay-0000001F-QINU`"'

This article in other languages

Language: English  • español • français • русский • 中文 • 中文(繁體)‎