Page 1 of 1

Let's develop together: Guppy's Multiple Moving Average

PostPosted: Fri Feb 26, 2010 7:18 pm
by Nikolay.Gekht
Aarnog wrote:
>Guppy's Multiple Moving Average (GMMA). It consists of:
>
>6 blue [short] EMA's - 3, 5, 8, 10, 12, 15 periods apiece.
>6 Red [long] EMA's - 30, 35, 40, 45, 50, 60 apiece.
>
>That's all. I figure it should be relatively simple to do.

Yeah, it’s really simple to do, so let’s start.

(Note: the full source code for this indicator is at the end of this topic)
(Note: Click on an image's thumbnail to open the full-size image)

At first, please read the article Definitions in the Indicore SDK documentation.

Then, we must “introduce” our indicator to the Marketscope. We would like to call it “GMMA” or “Guppy's Multiple Moving Average”.

GMMA is a short name of the indicator. The short name must be also the file name, so take your favorite text editor and create a file called “GMMA.lua”.

img1.png


All “introducing” information must be specified in the Init function of the indicator, so let’s write it. Define a “human-friendly” name first.

img2.png


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

img3.png



Then we must tell how to place our indicator on the chart. Our indicator will process the data which 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 will recommend to draw the indicator over the prices.

img4.png


And the last, but not least is to tell the Marketscope which parameters must be asked when the indicator is applied to the chart. Our indicator requires only two parameters: a color for the short EMA’s set and a color for the long EMA’s set. Let’s recommend blue for the first and red for the last parameter.
img5.png


That’s all, now the Marketscope knows everything it needs to let the user apply our indicator to the chart.

The next step is to describe what to do when the user has already applied the indicator to the price chart. There is the Prepare function which is called every time the indicator is applied (or every time the user changes the instrument or the period of the chart).

At this step we have to tell the Marketscope what we will draw and prepare our code for calculation of the values in future.

Let’s start. At the first step add the Prepare function and put the price data into the global variable for easier access in future.

img6.png


Then we have to create 12 lines which will show our EMA’s indicators. We have to remember the tables returned by the method (addStream) which is used for creating the indicator lines. These tables will be 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.

Follow me. The EMA’s formula is:
PRICE * K – PREVIOUS EMA x (1 - K),
where K is (2 /(PERIODS + 1))

The first EMA (when the PREVIOUS EMA is not defined) is calculated as the simple moving average for the PERIODS.
So, until we don’t have, for example, 3 periods of the prices for a 3-period EMA, we can’t start the calculation at all. So, the first period when we can start the calculation is period 2 (the period numeration is started from 0 (the oldest available price), so we have 3 periods: 0, 1 and 2).

Because the source can also start later than period 0 (for example when our indicator is applied to the results of another indicator), the first period when we can calculate EMA(3) is
FIRST SOURCE + 3 – 1.

We also store all our EMAs into the array and introduce the CreateEMA function to avoid duplicating the same code multiple times.

img7.png


Now we can create our lines:

img8.png


That’s all, our indicator explained what to draw and is ready to calculate the lines. Now we 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 will be called for each period of the source price. Then it will be 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 – do nothing.
2) If it’s the first period we can calculate – calculate the simple moving average
3) If it’s the next period(s) – calculate the EMA for the specified period.

img9.png


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

img10.png


Install the indicator to the Marketscope and try it:

img11.png


Everything looks fine except the name of the indicator in the top left corner of the chart area. We have just forgotten to tell the Marketscope how to call this instance of the indicator (it usually differs from the short name of the indicator because it also contains the specified parameters, for example MVA is an indicator, MVA(EUR/USD, 5) is a 5-period MVA applied to EUR/USD). Just add a few lines to the code:

img12.png


Voila, the indicator is ready!

The code is below (copy-paste it into GMMA.lua):
Code: Select all
function Init()
    indicator:name("Guppy's Multiple Moving Average");
    indicator:requiredSource(core.Tick);
    indicator:type(core.Indicator);

    indicator.parameters:addColor("S_COLOR", "Color for the short EMA group", "", core.rgb(0, 0, 255));
    indicator.parameters:addColor("L_COLOR", "Color for the long EMA group", "", core.rgb(255, 0, 0));
end

local source = nil;
local EMAs = {};    -- an array of outputs

function CreateEMA(index, N, color, name)
    local label;
    -- line label
    label = "EMA" .. N;
    -- create the line
    EMAs[index] = instance:addStream(label, core.Line, name .. label, label,
                                     color, source:first() + N - 1);
end

function Prepare()
    source = instance.source;
    local name;

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

    CreateEMA(0, 3, instance.parameters.S_COLOR, name);
    CreateEMA(1, 5, instance.parameters.S_COLOR, name);
    CreateEMA(2, 8, instance.parameters.S_COLOR, name);
    CreateEMA(3, 10, instance.parameters.S_COLOR, name);
    CreateEMA(4, 12, instance.parameters.S_COLOR, name);
    CreateEMA(5, 15, instance.parameters.S_COLOR, name);

    CreateEMA(6, 30, instance.parameters.L_COLOR, name);
    CreateEMA(7, 35, instance.parameters.L_COLOR, name);
    CreateEMA(8, 40, instance.parameters.L_COLOR, name);
    CreateEMA(9, 45, instance.parameters.L_COLOR, name);
    CreateEMA(10, 50, instance.parameters.L_COLOR, name);
    CreateEMA(11, 60, instance.parameters.L_COLOR, name);
end

function CalcEMA(index, N, period)
    local first;

    first = source:first() + N - 1;
    if period < first then
       return ;
    elseif period == first then
       -- range: period - N + 1, period - N + 2, ..., period
       local range = core.rangeTo(period, N);
       EMAs[index][period] = core.avg(source, range);
    else
       local k;
       k = 2.0 / (N + 1.0);
       -- EMA - PRICE * K - PREV EMA * (1 - K)
       EMAs[index][period] = source[period] * k + EMAs[index][period - 1] * (1 - k);
    end
end

function Update(period)
    CalcEMA(0, 3, period);
    CalcEMA(1, 5, period);
    CalcEMA(2, 8, period);
    CalcEMA(3, 10, period);
    CalcEMA(4, 12, period);
    CalcEMA(5, 15, period);

    CalcEMA(6, 30, period);
    CalcEMA(7, 35, period);
    CalcEMA(8, 40, period);
    CalcEMA(9, 45, period);
    CalcEMA(10, 50, period);
    CalcEMA(11, 60, period);
end


Or download it:
GMMA.lua
(2.52 KiB) Downloaded 1391 times

Re: Let's develop together: Guppy's Multiple Moving Average

PostPosted: Fri Feb 26, 2010 8:02 pm
by miocker
thank for the article. just one small question what does double dots mean:
label = "EMA" .. N;

Re: Let's develop together: Guppy's Multiple Moving Average

PostPosted: Fri Feb 26, 2010 8:03 pm
by Nikolay.Gekht
:-) It's very easy. This is the string concatenation in lua.
For example "a".."b" produces "ab".
See also here: http://www.fxcodebase.com/documents/ind ... 2.5.4.html

Re: Let's develop together: Guppy's Multiple Moving Average

PostPosted: Tue Apr 20, 2010 9:25 am
by LordTwig
wow, I like this step by step explaination of how to create an Indicator.

Can you do the same step by step explaination for using (calling) another indicator (core to table function) inside a new indicator, like the pivot, so that the pivot value can be used in calculations. (just something easy)

I still can't seem to do it, so a step through like the one you decribed would be great for everyone to see.

Cheers
LordTwig

Re: Let's develop together: Guppy's Multiple Moving Average

PostPosted: Tue Apr 20, 2010 9:58 pm
by Nikolay.Gekht
Creating another indicator is very easy in the most cases. Please, read the instruction below and feel free to point the moments where the description is not pretty clear as you wish.

Let's add the... MACD. Ok?

As we know, the indicator:
a) requires the source.
b) requires the parameters.
c) produces the output.

In case we want to create the indicator - we must do all these things "manually". The first thing I recommend to do is to look at the indicator source carefully and find how much parameters does it need.

In case of MACD we see in the code:
Code: Select all
    indicator:requiredSource(core.Tick);
    ...
    indicator.parameters:addGroup("Calculation");
    indicator.parameters:addInteger("SN", resources:get("param_SN_name"), resources:get("param_SN_description"), 12, 2, 1000);
    indicator.parameters:addInteger("LN", resources:get("param_LN_name"), resources:get("param_LN_description"), 26, 2, 1000);
    indicator.parameters:addInteger("IN", resources:get("param_IN_name"), resources:get("param_IN_description"), 9, 2, 1000);
    indicator.parameters:addGroup("Style");
    indicator.parameters:addColor("MACD_color", resources:get("param_MACD_color_name"), resources:get("param_MACD_color_description"), core.rgb(255, 0, 0));
    indicator.parameters:addColor("SIGNAL_color", resources:get("param_SIGNAL_color_name"), resources:get("param_SIGNAL_color_description"), core.rgb(0, 0, 255));
    indicator.parameters:addColor("HISTOGRAM_color", resources:get("param_HISTOGRAM_color_name"), resources:get("param_HISTOGRAM_color_description"), core.rgb(0, 255, 0));


So, it requires three calculation and three display parameters. We can ignore all display parameters, we do not plan do display it. :-)

So, only 3 parameters is requires - fast ema, slow ema and singal ema periods.

Also, we spotted that indicator requires a tick source. So, we can use our own source is our indicator is a tick indicator too, or have to choose one of ticks source of our bar source (open, close, high, low, median, typical, weighted).

The last thing is the short name (identifier). It is the simplest thing. The indicator identifier is the file name of the indicator (without .lua extension), in the upper case.

Well. We know everything we need. Now we must find a moment to create the indicator. The good point is the Prepare() function. At this moment our indicator knows it's own parameters and the price source it is applied at.

All indicators are available via core.indicators table. The table has the function create for the fast creation of the indicator. The first parameter is ID ("MACD" in our case), the next - is the price and then we can specify up to 5 parameters of the indicator.

So, declare a global variable to keep the indicator and apply MACD on your source (P1, P2, P3 are values for the indicator parameters).

Code: Select all
local MACD = nil;

function Prepare()
    ...
    MACD = core.indicators:create("MACD", source, P1, P2, P3);
end


Ok. Now we need to get the data. First, we have to kick the indicator to be calculated. It is reasonable to do it in the same time when our own indicator is recalculated.

Code: Select all
function Update(period, mode)
    MACD:update(mode);


Now we ready to get the data. Indicator has one or more streams. To get the stream use :getStream() method of the indicator's table. MACD has three streams (look at the MACD code again:)
Code: Select all
    MACD = instance:addStream("MACD", core.Line, name .. ".MACD", "MACD", instance.parameters.MACD_color, firstPeriodMACD);
    ...
    SIGNAL = instance:addStream("SIGNAL", core.Line, name .. ".SIGNAL", "SIGNAL", instance.parameters.SIGNAL_color, firstPeriodSIGNAL);
    HISTOGRAM = instance:addStream("HISTOGRAM", core.Bar, name .. ".HISTOGRAM", "HISTOGRAM", instance.parameters.HISTOGRAM_color, firstPeriodSIGNAL);


So, to get the MACD line get the stream 0, to get the SIGNAL line - get the stream 1, and to get the HISTOGRAM - get the stream 2.

You can operate these streams exactly the same way as you operate the tick source, i.e. get value for the bar and so on. The bar index is exactly the same as the bar index of the indicator's source.

For example we would like to check that HISTOGRAM is equal to 0. (Oh, do not forget to check that the indicator's data are available for the bar!)

Code: Select all
function Update(period, mode)
    MACD:update(mode);
   if period >= MACD:getStream(2):first() then
       if MACD:getStream(2)[period] == 0 then
            ...
       end
    end


BTW, the first stream of the indicator is available via MACD.DATA

MACD indicator itself is also example of the indicator usage. It creates two EMA indicators applied on the source and then another MVA indicator applied on the internal stream (to calculate the signal line). Pay attention that it calls the update for MVA's indicator only when this internal stream is calculated.

PIVOT is a bit complexer indicator. :-( It requires loading additional data (for example day data for hour chart, so, it is not available immediately after calling the update() method of the indicator. I never tried to re-use such indicator, so, not sure whether Marketscope will force updating the calling indicator in the correct manner. I have to check it. I'll try to provide an example of the PIVOT reusage with detailed comments asap.

Re: Let's develop together: Guppy's Multiple Moving Average

PostPosted: Wed Apr 21, 2010 9:16 am
by LordTwig
Okay thanks for that Nikolay, it is so much easier to see how it is done. I'll give it a whirl and practice on some indicators.

Yeah.... I though that Pivot was hard to do, I appreciate your time given already and that would be great if you can do a step by step on it later on.

Regards
LordTwig

Re: Let's develop together: Guppy's Multiple Moving Average

PostPosted: Mon Apr 04, 2011 5:12 am
by davetherave110179
Hi,

This section is great! Can this method of indicator creation be used to merge two indicators together??

davetherave110179

Re: Let's develop together: Guppy's Multiple Moving Average

PostPosted: Mon Apr 04, 2011 9:58 am
by Apprentice
I'm not sure what you think.
Can you elaborate.

Re: Let's develop together: Guppy's Multiple Moving Average

PostPosted: Tue Apr 05, 2011 7:53 am
by Nikolay.Gekht
davetherave110179 wrote:Hi,

This section is great! Can this method of indicator creation be used to merge two indicators together??

davetherave110179


If you want just copy and join logic - yes. But, probably, all you need is need an example how to use another indicator in your code. Please see new version of this article:
http://www.fxcodebase.com/wiki/index.ph ... _%28GMA%29
It additionally demonstrates how to replace own EMA implementation with the standard MA indicator.