Developing, Debugging and Testing your first strategy (upd)

Moderator: admin

Developing, Debugging and Testing your first strategy (upd)

Postby Nikolay.Gekht » Thu Sep 02, 2010 8:51 pm

Developing, Debugging and Testing Your First Strategy

This article guides you trough all stages of strategy development: writing code, debugging and back testing.

Prerequisites

Please download and install Indicator SDK. You can download it by this link: download

Please familiarize yourself with the indicators development first. Read and try at least the first article of the listed below.
Guppy's Multiple Moving Average
Piercing Line pattern detection
Yesterday's Close Value

Strategy to Develop

Let’s develop the simplest strategy which buys when the fast moving average crosses over the slow moving average and sells when the fast moving average crosses under the slow moving average. The user can choose the timeframe at which the strategy can be applied, the account to trade on, the lot size to trade, and setup stop and limit orders.

Our strategy can work on both NFA-regulated and non-NFA accounts. Since the regular stop and limits aren’t available for NFA-regulated accounts, the strategy uses the ELS (Entry Limit Stop) order to handle risk management on NFA-regulated accoutns.

Important note about the timeframe: The user can choose either “tick” or “bar” source. In case the “bar” source is chosen, we take into account the recently closed candle only. Of course, the strategy can work in such way that the currently changing candle can be processed as well, but for many applications such approach provides too many “noisy” signals (we’ve discussed this approach in a separate article).

Step 1. Writing Source Code

The strategy source is a plain text file which contains the source code written in Lua. You can use any of your preferable text editors. The syntax highlighting is welcome, but not necessary. In this article I use the SDK’s editor, but you can easily do the same steps in any other programmer’s editor.

So, first, start the editor application. (StartMenu->Programs->IndicoreSDK->Lua Editor).

Let’s name the file and save it into the proper place first. Choose File->Save As command. The “Save As” form appears. By default, the editor opens and saves files from the indicators’ folder of SDK. Strategy must be saved into the strategies’ folder of the SDK, so go to the parent folder and then to the sub-folder named “strategies”. Then name the strategy. This name must be unique, so choose it carefully. Let’s name our strategy - “sample”.
step1a-save.png


Now we can create the “strategy skeleton”. There are a number of functions we must implement in the strategy.

The first function is Init(). This function “introduces” the strategy for Marketscope. It is called once, when Marketscope initially loads the strategy. This function must provide the strategy name, description and the set of parameters which must be prompted when the user runs the strategy.

The second function is Prepare(). This function is called every time when the user filled the parameters but before the first price update. Your strategy must validate all the parameters’ values and prepare all the information for further execution.

The third function is ExtUpdate(). This function is called every time another tick (for tick price sources) comes or another bar (for bar sources) is closed.

And finally, we must include the Lua’s “helper” functions which simplify the strategy development process.

Note: Yes, I know. You have already read the “strategy structure” article in the user guide and wonder where the Update() function is. Ok. This function is a part of “pure” strategies which listen only ticks and manage other timeframes by themselves. We are using a simpler approach in this example, based on the above mentioned helper functions. You can read about these helpers here in this article in the user guide.

step1b-sceleton.png


Code: Select all
function Init()
    strategy:name("Sample Moving Average Strategy");
    strategy:description("Just a sample which buys on fast crosses over slow and sells when slow crosses over fast");
end

function Prepare()
end

function ExtUpdate(id, source, period)
end

dofile(core.app_path() .. "\\strategies\\standard\\include\\helper.lua");


Now, let’s prepare the list of parameters to be prompted when the user applies our strategy. The instrument will be prompted automatically, but there is a lot of information to ask:

We must know:
1) The number of periods to calculate fast and slow moving averages.
2) The kind of the price (bid or ask) and the time frame the user wants to apply our strategy on.
3) Whether the user wants to see text alert and/or sound when the trading condition is met.
4) Whether the user wants to let our strategy trade.
5) The account to trade on.
6) The default trade size.
7) Whether the user wants to use stop and limit orders for the risk management.

All these things to be prompted are called parameters. The parameters can be an integer or a real number, a string, a file name, a Boolean (yes/no) value and even the so-called “lists”. “A list” is when the user doesn’t enter the value manually but chooses one of the values we provided as an “alternatives”. Also, some parameters can have additional predefined behavior. For example, the parameter enables the user to choose one of the existing accounts or one of the supported time frames. To activate such predefined behavior, we use “flags”.

Each parameter is described by the identifier. We will use this identifier to get the parameter value. This must be formed using the same rules as the Lua identifier (can include English letters, numbers and underscore sing). Also, we must provide the name and the description to be shown to the user, and the default value. The numeric parameters can also have minimum and maximum values.

So, just add the parameter set to the Init() function. Please note that we split parameters into “groups”, so the user could navigate through them a bit easier.
Code: Select all
    strategy.parameters:addGroup("Moving Average Parameters");
    strategy.parameters:addInteger("F", "Fast Periods", "", 5, 1, 200);
    strategy.parameters:addInteger("S", "Slow Periods", "", 20, 1, 200);

    strategy.parameters:addGroup("Price");
    strategy.parameters:addString("PT", "Price Type", "", "Bid");
    strategy.parameters:addStringAlternative("PT", "Bid", "", "Bid");
    strategy.parameters:addStringAlternative("PT", "Bid", "", "Ask");
    strategy.parameters:addString("TF", "Time Frame", "", "m1");
    strategy.parameters:setFlag("TF", core.FLAG_PERIODS);

    strategy.parameters:addGroup("Signals");
    strategy.parameters:addBoolean("ShowAlert", "Show Alert", "", true);
    strategy.parameters:addBoolean("PlaySound", "Play Sound", "", false);
    strategy.parameters:addFile("SoundFile", "Sound File", "", "");
    strategy.parameters:setFlag("SoundFile", core.FLAG_SOUND);


    strategy.parameters:addGroup("Trading");
    strategy.parameters:addBoolean("CanTrade", "Allow Trading", "", true);
    strategy.parameters:addString("Account", "Account to trade", "", "");
    strategy.parameters:setFlag("Account", core.FLAG_ACCOUNT);
    strategy.parameters:addInteger("LotSize", "Size of the trade in lots", "", 1, 1, 100);
    strategy.parameters:addInteger("Stop", "Distance in pips for the stop order", "Use 0 to do not use stops", 0, 1, 100);
    strategy.parameters:addInteger("Limit", "Distance in pips for the limit order", "Use 0 to do not use limits", 0, 1, 100);


So, we have introduced the strategy to Marketscope. Marketscope can show our strategy in the list and can show the parameters and prompt for their values when the user wants to run the strategy.

Now let’s start working on the Prepare() method. Let’s go step-by-step.

First, check whether the fast moving average is really faster than the slow.
Code: Select all
    local F, S;
    F = instance.parameters.F;
    S = instance.parameters.S;
    assert(F < S, "The fast moving average must be faster than the slow");


Note: The assert function throws an error with the text specified in the second parameter when the condition specified in the first parameter fails.

Then check the sound alert parameters. The sound file must be specified in case the user requested to play the sound. Also, keep the sound file name for further usage in the global variable.
Code: Select all
local SoundFile;

function Prepare()
    ...
    local ShowAlert;
    ShowAlert = instance.parameters.ShowAlert;
    if instance.parameters.PlaySound then
        SoundFile = instance.parameters.SoundFile;
    else
        SoundFile = nil;
    end
    assert(not(PlaySound) or (PlaySound and SoundFile ~= ""), "Sound file must be specified");


Then check whether we can trade and prepare all the trading related-data. Keep the trading data in the global variables as well. Just collect the data first.
Code: Select all
local CanTrade;
local Amount;
local Account;
local Stop;
local Limit;
local CanClose;
local CanStop;
local OfferID;

function Prepare()
    ...
    if CanTrade then
        Account = instance.parameters.Account;
        Stop = math.floor(instance.parameters.Stop + 0.5);
        Limit = math.floor(instance.parameters.Limit + 0.5);
        local instrument = instance.bid:instrument();
    end


Now we have the instrument name (for example, EUR/USD). But for trading operations we must have OfferID – the identifier of the instrument inside the trading system. We can get this identifier in the offers trading table. Use the host table to get the offers trading table and then find the instrument name inside this table.
Code: Select all
        OfferID = core.host:findTable("offers"):find("Instrument", instrument).OfferID;


The code line above gets the “offers” trading table, then searches inside this trading table for a row in which the “instrument” column is equal to our instrument and then takes “OfferID” column of that row. We don’t check for successful search here because if the user can choose the instrument, this instrument definitely exists in the offers trading table.

Then we have to check whether regular Stop and Limit orders can be used. Some accounts are under NFA Rule 2-43(b), so the regular Stop and Limit orders cannot be created for such accounts. We will use ELS (Entry Limit Stop) orders for risk management on NFA-regulated accounts. We can use the host:execute(“getTradingProperty”) method to check almost all trading-related rules.

Let’s check whether regular stop, limit and close orders are permitted.
Code: Select all
        CanStop = core.host:execute("getTradingProperty", "canCreateStopLimit", instrument, Account);
        CanClose = core.host:execute("getTradingProperty", "canCreateMarketClose", instrument, Account);


Then, we must convert the amount specified in lots into the absolute amount as the trading system requires for the orders. We use the trading properties again to convert the lot into the absolute amount for the particular instrument and account.

Code: Select all
        Amount = instance.parameters.LotSize * core.host:execute("getTradingProperty", "baseUnitSize", instrument, Account);


Then we must provide the name of the instance of the strategy. Usually, the instance is named as the strategy identifier plus the set of parameters used to run the strategy. Just make the name and then set it up for the strategy instance and for the alerts. Alerts will show this name in front of the message.

Code: Select all
    local name = profile:id() .. "(" .. instance.bid:name() .. "," .. instance.parameters.TF .. "," .. F .. "," .. S .. ")";
    instance:name(name);
    ExtSetupSignal(name, ShowAlert);


And, finally, we have to subscribe for tick updates to let our strategy be activated the first time.

Code: Select all
ExtSubscribe(1, nil, "t1", true, "tick");


Please, pay attention, we do NOT subscribe for the chosen time frame right in the Prepare() function. This function is also called every time the user changes any parameter in the “Add Strategy” function, so, if we put our subscription code into the Prepare() function, it will subscribe/unsubscribe multiple times, every time the user changes the parameter value. The tick subscription is always available for the strategy, so it does not increase resource usage, unlike any bar subscription.

Ok. We have finished with the Prepare() function.

Let’s start with the strategy logic. All the logic must be done inside the ExtUpdate() function or the functions called from this function. This function is called every time a new tick appears (for the tick subscriptions) or the bar has been closed (so the strategy is called only once for each bar).

The first thing to do is to subscribe to the prices in the timeframe which were actually chosen by the user.

Add a global variable to keep the reference to the price history we are subscribed for. Also, add the global variables to keep the indicators we want to create. The tick subscription has identifier 1 (see ExtSubscribe function above), so subscribe for the prices if the strategy is called for the update of subscription 1 and we haven’t subscribed for the chosen time frame yet.

Code: Select all
local price = nil;
local fastMA;
local slowMA;
local first;

function ExtUpdate(id, source, period)
    if id == 1 and price == nil then
        price = ExtSubscribe(2, nil, instance.parameters.TF, true, "close");
        fastMA = core.indicators:create("MVA", price, instance.parameters.F);
        slowMA = core.indicators:create("MVA", price, instance.parameters.S);
        first = math.max(fastMA.DATA:first(), slowMA.DATA:first()) + 1;
    end
end


The code above subscribes for the prices in the chosen time frame, requests only close prices for the bars (or just ticks in case the user has chosen ticks) and then creates two MVA indicators applied on these prices. Please note that we also keep the “first” value. The first value is the oldest bar for which the indicator can be calculated. For example, MVA(7) cannot be calculated for the first 6 bars, because it’s a sum of the last 7 bars (including the current) divided by 7. “+1” is because we plan to check “cross” condition which requires that the previous bar value is also available. So, for MVA(7) the first bar we can process is 8th bar.

Now we can provide our logic. But before writing the logic itself, let’s create a couple of functions to trade. One function is for opening a trade and another for closing all existing trades in the specified direction.

To open the trade, we must know direction (buy or sell), amount, account and offer. We already know everything except the direction. So, the only parameter of our function must be a flag indicating whether a sell or a buy position must be opened. To execute the trading command, we use terminal:execute() method. First of all, we must create and fill a valuemap with all known parameters (order type (“Open Market”), account, offer and amount:

Code: Select all
function open(sell)
   
    valuemap = core.valuemap();
    valuemap.Command = "CreateOrder";
    valuemap.OrderType = "OM";
    valuemap.OfferID = OfferID;
    valuemap.AcctID = Account;
    valuemap.Quantity = Amount;
    ...
end


Then we must write the side – “S” for sell order and “B” for buy order and calculate the stop and limit levels against the current bid/ask prices using the offset of the stop and limit orders specified in pips in the parameters. Pay attention that we calculate stop and limit levels against different prices to avoid having the stop or limit inside the spread. If the account is NFA-regulated, we should set the valuemap.EntryLimitStop parameter to "Yes" to create ELS Limit/Stop order.

Code: Select all
    local side, limit, stop;
    if sell then
        side = "S";
        stop = instance.ask[instance.ask:size() - 1] + Stop * instance.ask:pipSize();
        limit = instance.bid[instance.ask:size() - 1] - Limit * instance.bid:pipSize();
    else
        side = "B";
        stop = instance.bid[instance.ask:size() - 1] - Stop * instance.ask:pipSize();
        limit = instance.ask[instance.ask:size() - 1] + Limit * instance.bid:pipSize();
    end

    valuemap.BuySell = side;

    if Stop > 0 and CanStop then
        valuemap.RateStop = stop;
    end

    if Limit > 0 and CanStop then
        valuemap.RateLimit = limit;
    end

    if (not CanStop) and (Stop > 0 or Limit > 0) then
        valuemap.EntryLimitStop = 'Y';
    end


Now the order is ready to be sent. Just do it and check whether the order parameters have been filled successfully.

Code: Select all
    local success, msg;
    success, msg = terminal:execute(200, valuemap);
    assert(success, msg);


Now collect all code above into the function.

Code: Select all
function open(sell)
    local valuemap;
    valuemap = core.valuemap();
    valuemap.Command = "CreateOrder";
    valuemap.OrderType = "OM";
    valuemap.OfferID = OfferID;
    valuemap.AcctID = Account;
    valuemap.Quantity = Amount;
   
    local side, limit, stop;
    if sell then
        side = "S";
        stop = instance.ask[instance.ask:size() - 1] + Stop * instance.ask:pipSize();
        limit = instance.bid[instance.ask:size() - 1] - Limit * instance.bid:pipSize();
    else
        side = "B";
        stop = instance.bid[instance.ask:size() - 1] - Stop * instance.ask:pipSize();
        limit = instance.ask[instance.ask:size() - 1] + Limit * instance.bid:pipSize();
    end

    valuemap.BuySell = side;

    if Stop > 0 and CanStop then
        valuemap.RateStop = stop;
    end

    if Limit > 0 and CanStop then
        valuemap.RateLimit = limit;
    end

    if (not CanStop) and (Stop > 0 or Limit > 0) then
        valuemap.EntryLimitStop = 'Y';
    end
   
    local success, msg;
    success, msg = terminal:execute(200, valuemap);
    assert(success, msg);
end


Now we can create the function for closing the trades. We need to use “CM” (close market) order for each trade which is opened in the specified direction on the specified account and the specified instrument. To get a list of all open trades, we must enumerate the “trades” trading table.

Code: Select all
function close(sell)
    local enum, side;

    if sell then
        side = "S";
    else
        side = "B";
    end

   enum = core.host:findTable("trades"):enumerator();
   while true do
        row = enum:next();
        if row == nil then
            break;
        end
        if row.AccountID == Account and row.OfferID == OfferID and row.BS == side then
            -- row contains an open trade on our account and instrument in the specified direction (buy or sell).
        end
   end 
end 


Now add the close order for each trade which must be closed.

Code: Select all
function close(sell)
    local enum, side, valuemap;

    if sell then
        side = "S";
    else
        side = "B";
    end

   enum = core.host:findTable("trades"):enumerator();
   while true do
        row = enum:next();
        if row == nil then
            break;
        end
        if row.AccountID == Account and row.OfferID == OfferID and row.BS == side then
                valuemap = core.valuemap();
                valuemap.Command = "CreateOrder";
                valuemap.OrderType = "CM";
                valuemap.OfferID = OfferID;
                valuemap.AcctID = Account;
                valuemap.Quantity = row.Lot;
                valuemap.TradeID = row.TradeID;
                if row.BS == "B" then
                    valuemap.BuySell = "S";
                else
                    valuemap.BuySell = "B";
                end
                local success, msg;
                success, msg = terminal:execute(200, valuemap);
                assert(success, msg);
        end
   end 
end 


Now we can close all the existing trades in the specified direction. But what about NFA accounts, for which we just cannot use close orders? It’s simple. Just create an opposite open market orders! We can do it because the accounts with the close market orders disabled are never hedging accounts. So, the final version of the close function is:

Code: Select all
function close(sell)
    local enum, row, valuemap, side, closed;
    local valuemap;

    if sell then
        side = "S";
    else
        side = "B";
    end
    closed = false;

    enum = core.host:findTable("trades"):enumerator();
    while true do
        row = enum:next();
        if row == nil then
            break;
        end
        if row.AccountID == Account and
           row.OfferID == OfferID and
           row.BS == side then
            if CanClose then
                -- non-NFA accounts
                valuemap = core.valuemap();
                valuemap.Command = "CreateOrder";
                valuemap.OrderType = "CM";
                valuemap.OfferID = OfferID;
                valuemap.AcctID = Account;
                valuemap.Quantity = row.Lot;
                valuemap.TradeID = row.TradeID;
                if row.BS == "B" then
                    valuemap.BuySell = "S";
                else
                    valuemap.BuySell = "B";
                end
                local success, msg;
                success, msg = terminal:execute(200, valuemap);
                assert(success, msg);
            else
                -- NFA accounts
                valuemap = core.valuemap();
                valuemap.OrderType = "OM";
                valuemap.Command = "CreateOrder";
                valuemap.OfferID = OfferID;
                valuemap.AcctID = Account;
                valuemap.Quantity = row.Lot;
                if row.BS == "B" then
                    valuemap.BuySell = "S";
                else
                    valuemap.BuySell = "B";
                end
                local success, msg;
                success, msg = terminal:execute(200, valuemap);
                assert(success, msg);
            end
            closed = true;
        end
    end
    return closed;
end


So, we have all required things. Now just add the code which is activated every time another candle is closed. As you remember, we subscribed for the history with identifier “2”. So, every time the candle is closed, we must update the indicators and then check whether these indicators cross.

So, let’s return to the ExtUpdate function and add the following code there.
Code: Select all
    elseif id == 2 then
        fastMA:update(core.UpdateLast);
        slowMA:update(core.UpdateLast);
        if period >= first then
           if core.crossesOver(fastMA.DATA, slowMA.DATA, period) then
               ExtSignal(instance.ask, instance.ask:size() - 1, "BUY", SoundFile, nil);
           elseif core.crossesUnder(fastMA.DATA, slowMA.DATA, period) then
               ExtSignal(instance.bid, instance.bid:size() - 1, "SELL", SoundFile, nil);
           end
       end


Now the strategy shows the alert requested (text, sound or both) when indicators cross. We also must add the trading code in case the trading is allowed. For buy signal we must close all sell positions or open new buy in case there is no sell position. For the sell signal we must close all buy positions or open a sell position in case there is no buy position.

So, add a flag into the “close” function which indicates whether any of the positions are closed. Then add the following code inside “buy” if statement

Code: Select all
               if CanTrade then
                   if not(close(true)) then
                       open(false);
                   end
               end


And, accordingly, add the following code inside “sell” if statement

Code: Select all
               if CanTrade then
                   if not(close(false)) then
                       open(true);
                   end
               end


That’s all. Now see the whole code of our strategy. I also added a few comments to make the navigation trough the code easier.

Download the code:
sample.lua
(12.67 KiB) Downloaded 1396 times



Code: Select all
-- Intoduce the strategy to the host application (for example, Marketscope).
-- The function is called once when the host application initially loads the strategy.
function Init()
    -- User-friendly name and the description
    strategy:name("Sample Moving Average Strategy");
    strategy:description("Just a sample which buys on fast crosses over slow and sells when slow crosses over fast");

    -- Fast and slow moving average parameters
    strategy.parameters:addGroup("Moving Average Parameters");
    strategy.parameters:addInteger("F", "Fast Periods", "", 5, 1, 200);
    strategy.parameters:addInteger("S", "Slow Periods", "", 20, 1, 200);

    -- Price subscription parameters (bid or ask price, time frame)
    strategy.parameters:addGroup("Price");
    strategy.parameters:addString("PT", "Price Type", "", "Bid");
    strategy.parameters:addStringAlternative("PT", "Bid", "", "Bid");
    strategy.parameters:addStringAlternative("PT", "Ask", "", "Ask");
    strategy.parameters:addString("TF", "Time Frame", "", "m1");
    strategy.parameters:setFlag("TF", core.FLAG_PERIODS);

    -- Alert parameters
    strategy.parameters:addGroup("Alerts");
    strategy.parameters:addBoolean("ShowAlert", "Show Alert", "", true);
    strategy.parameters:addBoolean("PlaySound", "Play Sound", "", false);
    strategy.parameters:addFile("SoundFile", "Sound File", "", "");
    strategy.parameters:setFlag("SoundFile", core.FLAG_SOUND);

    -- Trading parameters
    strategy.parameters:addGroup("Trading");
    strategy.parameters:addBoolean("CanTrade", "Allow Trading", "", true);
    strategy.parameters:addString("Account", "Account to trade", "", "");
    strategy.parameters:setFlag("Account", core.FLAG_ACCOUNT);
    strategy.parameters:addInteger("LotSize", "Size of the trade in lots", "", 1, 1, 100);
    strategy.parameters:addInteger("Stop", "Distance in pips for the stop order", "Use 0 to do not use stops", 0, 0, 100);
    strategy.parameters:addInteger("Limit", "Distance in pips for the limit order", "Use 0 to do not use limits", 0, 0, 100);
end

-- The global variables
local price = nil;      -- the price history we subscribed for
local fastMA;           -- fast moving average indicator
local slowMA;           -- slow moving average indicator
local first;            -- the index of the oldest period where we can check whether moving averages has been crossed
local CanTrade;         -- flag indicating whether we can trade
local Amount;           -- the amount for the trade
local Account;          -- the account to trade on
local Stop;             -- the stop order level expressed in pips or 0 if no stop must be used
local Limit;            -- the limit order level expressed in pips or 0 if no stop must be used
local CanClose;         -- the flag indicating whether "close market" orders is allowed
local CanStop;
local OfferID;          -- the internal indentifier of the instrument (required for the orders)
local SoundFile;        -- the sound file name or nil if no sound must be played

-- Prepare all the data.
-- The function is called once when the strategy is about to be started.
function Prepare()
    -- check moving average parameters
    local F, S;
    F = instance.parameters.F;
    S = instance.parameters.S;
    assert(F < S, "The fast moving average must be faster than the slow");

    -- check alerts settings
    local ShowAlert;
    ShowAlert = instance.parameters.ShowAlert;
    if instance.parameters.PlaySound then
        SoundFile = instance.parameters.SoundFile;
    else
        SoundFile = nil;
    end
    assert(not(PlaySound) or (PlaySound and SoundFile ~= ""), "Sound file must be specified");

    -- check whether the strategy is allowed to trade
    CanTrade = instance.parameters.CanTrade;
    if CanTrade then
        -- Prepare the common information (account, stop, limit and OfferID (internal id of the instrument).
        Account = instance.parameters.Account;
        Stop = math.floor(instance.parameters.Stop + 0.5);
        Limit = math.floor(instance.parameters.Limit + 0.5);
        local instrument = instance.bid:instrument();
        OfferID = core.host:findTable("offers"):find("Instrument", instrument).OfferID;
        -- check whether stop and limit orders are allowed
        CanStop = core.host:execute("getTradingProperty", "canCreateStopLimit", instrument, Account);
        -- Check whether close market orders are allowed
        CanClose = core.host:execute("getTradingProperty", "canCreateMarketClose", instrument, Account);
        -- And finally turn "in lots" amount into the absolule value.
        Amount = instance.parameters.LotSize * core.host:execute("getTradingProperty", "baseUnitSize", instrument, Account);

    end
    -- name the indicator
    local name = profile:id() .. "(" .. instance.bid:name() .. "," .. instance.parameters.TF .. "," .. F .. "," .. S .. ")";
    instance:name(name);
    -- setup the signal. pay attention, we pass "ShowAlert" (value initially taken from the instance.parameters.ShowAlert)
    -- here, so, we don't check whether alerts are requested anymore.
    ExtSetupSignal(name, ShowAlert);
    -- and finally subscribe for the ticks of the instrument the user initially chosen to run the strategy for to
    -- have our strategy activated once.
    ExtSubscribe(1, nil, "t1", true, "tick");
end

-- the function is called every time when any subscribed price is changed. For tick subscribtions the function is called
-- for every tick, for the bar subscribtions the function is called when the candle is closed (in other words, when
-- the first tick of the next candle appears).
function ExtUpdate(id, source, period)
    if id == 1 and price == nil then
        -- our tick subscribtion. do it on the first tick if the
        -- user chosen subscribtion has not been not made yet.

        -- subscribe for the user chosen timeframe/to close prices
        price = ExtSubscribe(2, nil, instance.parameters.TF, true, "close");
        -- create indicators
        fastMA = core.indicators:create("MVA", price, instance.parameters.F);
        slowMA = core.indicators:create("MVA", price, instance.parameters.S);
        -- and get the oldest index of the bar we can work at
        first = math.max(fastMA.DATA:first(), slowMA.DATA:first()) + 1;
    elseif id == 2 then
        -- on the user chosen subscription (can be either tick or bar subscribtion).

        -- update indicators
        fastMA:update(core.UpdateLast);
        slowMA:update(core.UpdateLast);

        -- if we have enough bars in the history to work
        if period >= first then
            -- if fast moving average goes over slow moving average
            if core.crossesOver(fastMA.DATA, slowMA.DATA, period) then
                -- show the signal
                ExtSignal(instance.ask, instance.ask:size() - 1, "BUY", SoundFile, nil);
                -- and if the trading is allowed - either close all sell positions or
                -- open the buy one
                if CanTrade then
                    if not(close(true)) then
                        open(false);
                    end
                end

            -- if fast moving average goes under slow moving average
            elseif core.crossesUnder(fastMA.DATA, slowMA.DATA, period) then
                -- show the signal
                ExtSignal(instance.bid, instance.bid:size() - 1, "SELL", SoundFile, nil);
                -- and if the trading is allowed - either close all buy positions or
                -- open the sell one
                if CanTrade then
                    if not(close(false)) then
                        open(true);
                    end
                end
            end
        end
    end
end

-- The function opens the position in the chosen direction.
-- Account, amount and instrument (aka offer) are predefined in
-- the Prepare() function.
function open(sell)
    -- create and fill value map with the predefined order parameters
    local valuemap;
    valuemap = core.valuemap();
    valuemap.Command = "CreateOrder";       -- command: create new order
    valuemap.OrderType = "OM";              -- order type: open market (execute immediatelly, at any available market price)
    valuemap.OfferID = OfferID;             -- instrument
    valuemap.AcctID = Account;
    valuemap.Quantity = Amount;

    -- fill the side and calculate stops and limits.
    -- the Stop and Limit global variables are expressed in pips.
    -- pay attention that we calculate prices against different prices
    -- to avoid setting the stop or limit inside the spread.
    -- Also, the stop and limit offset depends on the trade direction. For example
    -- the stop for a buy position is below the price while the stop for a sell position
    -- is above the price.
    local side, limit, stop;
    if sell then
        side = "S";
        stop = instance.ask[instance.ask:size() - 1] + Stop * instance.ask:pipSize();
        limit = instance.bid[instance.ask:size() - 1] - Limit * instance.bid:pipSize();
    else
        side = "B";
        stop = instance.bid[instance.ask:size() - 1] - Stop * instance.ask:pipSize();
        limit = instance.ask[instance.ask:size() - 1] + Limit * instance.bid:pipSize();
    end

    -- fill side and stop/limit orders in the value map
    valuemap.BuySell = side;

    if Stop > 0 and CanStop then
        valuemap.RateStop = stop;
    end

    if Limit > 0 and CanStop then
        valuemap.RateLimit = limit;
    end

    if (not CanStop) and (Stop > 0 or Limit > 0) then
        valuemap.EntryLimitStop = 'Y';
    end

    -- and, finally, execute it
    local success, msg;
    success, msg = terminal:execute(200, valuemap);
    assert(success, msg);
end

-- close all the positions in the specified direction
function close(sell)
    local enum, row, valuemap, side, closed;
    local valuemap;

    -- just prepare the name of the direction ("S" for sell and "B" for buy).
    if sell then
        side = "S";
    else
        side = "B";
    end

    -- the flag indicating whether at least one position is closed.
    closed = false;

    -- get a enumerator for the "open trades" trading table
    enum = core.host:findTable("trades"):enumerator();

    -- and scan trough all table rows. one row is one trade.
    while true do
        row = enum:next();
        if row == nil then
            break;
        end

        -- if trade was placed for chosen account and instrument
        -- and the trade is in the requested side - close it
        if row.AccountID == Account and
           row.OfferID == OfferID and
           row.BS == side then

            -- use different close models depending on the account type
            if CanClose then
                -- non-NFA accounts, can use Market Close order
                valuemap = core.valuemap();
                valuemap.Command = "CreateOrder";
                valuemap.OrderType = "CM";      -- close market order
                valuemap.OfferID = OfferID;
                valuemap.AcctID = Account;
                valuemap.Quantity = row.Lot;    -- the size is required because we can close the position partially
                valuemap.TradeID = row.TradeID; -- the trade must be referenced int he close order
                -- the side of the close order must be opposite to the side of the trade.
                if row.BS == "B" then
                    valuemap.BuySell = "S";
                else
                    valuemap.BuySell = "B";
                end
                -- and execute
                local success, msg;
                success, msg = terminal:execute(200, valuemap);
                assert(success, msg);
            else
                -- NFA accounts. We cannot call close orders but can
                -- execute an opposite open order. Since NFA accounts can
                -- never be hedging accounts, the opposite open order
                -- closes the existing positions first.
                valuemap = core.valuemap();
                valuemap.OrderType = "OM";
                valuemap.Command = "CreateOrder";
                valuemap.OfferID = OfferID;
                valuemap.AcctID = Account;
                valuemap.Quantity = row.Lot;
                if row.BS == "B" then
                    valuemap.BuySell = "S";
                else
                    valuemap.BuySell = "B";
                end
                local success, msg;
                success, msg = terminal:execute(200, valuemap);
                assert(success, msg);
            end
            -- we closed the position, so we should not open anything.
            closed = true;
        end
    end
    return closed;
end

-- use the helpers
dofile(core.app_path() .. "\\strategies\\standard\\include\\helper.lua");
Last edited by Anonymous on Thu Dec 22, 2011 5:19 am, edited 1 time in total.
Reason: Added ELS stop/limit creation for NFA-regulated accounts
Nikolay.Gekht
FXCodeBase: Site Admin
 
Posts: 1235
Joined: Wed Dec 16, 2009 6:39 pm
Location: Cary, NC

Step 2: Debugging, Part 1

Postby Nikolay.Gekht » Thu Sep 02, 2010 8:54 pm

Step 2. Debugging Strategy

If you did everything right, the final code of the strategy is located in the “C:\Program Files\Gehtsoft\IndicoreSDK\strategies\” folder in the sample.lua file. Now let’s look how it works.

Run the strategy debugger (“Start->Programs->IndicoreSDK->Lua Strategy Debugger”). In the debugger window, choose “File->Open Strategy”. The debugger shows the list of the strategies which can be debugged. Choose the strategy and click “OK”.

step2b.png


The strategy is opened. In case any error appears, it will be shown in the output window. If there are no errors in the strategy, it will be loaded and execution will be suspended right before the first line of the strategy. You can now execute the strategy step by step or between break points.

step2c.png


Let’s skip the whole initialization routine. To do that, just scroll down to the first statement in the Prepare() function, put the cursor at the line with the first statement and then press F9 to put the break point.

step2d.png
Last edited by Anonymous on Thu Dec 22, 2011 5:51 am, edited 1 time in total.
Reason: Updated for the new version of Indicore (2.1)
Nikolay.Gekht
FXCodeBase: Site Admin
 
Posts: 1235
Joined: Wed Dec 16, 2009 6:39 pm
Location: Cary, NC

Debugging: Part 2

Postby Nikolay.Gekht » Thu Sep 02, 2010 8:57 pm

Now just press F5 (run) to execute the Init() function. When the Init() function is finished, the Parameters dialog box will appear.

step2e.png

First, we should choose the price source to simulate tick prices. To do so, place the cursor in the First Instrument Price Source parameter value field and click the ellipsis (...) button. The Choose Price Source dialog box appears:

step2e-1.png

You can choose the file with prices or use data from the quotes manager server:
  • To choose the price file, click File and then click the ellipsis (...) button. You can refer to the CodeBase Price Archive to get 1-year data files.
  • You can use the quotes manager server to download the 1-minute quote data for the chosen instrument in chosen period. To use the data from quotes manager, click Quotes Manager and then choose instrument, time frame and data range.
We will use the prices from the quotes manager server. After clicking OK, the message box asking whether you want to download the data now appears. Click Yes. You will see the Load Quotes window:

step2e-2.png
step2e-2.png (6.73 KiB) Viewed 12176 times

Click OK to start downloading the data for the chosen instrument and period. Wait a bit while the data is loaded.

Then scroll the list of the parameters to the “Time Frame” parameters and enter “m15” (15 minutes) time frame to apply our strategy on 15-minutes data.
Press OK to setup the parameters and go to the “Prepare” function.

Now look at another useful function of the debugger. You can check the value of any Lua expression anytime. Switch to the “Watches” tab and then use “Debug->Add Watch” command to add a Lua expression and see the value. All expressions entered into watches list are updated after every step, so you can monitor the status of your strategy anytime during the execution.

step2f.png

Ok. Let’s go ahead. Now try to stop at the following moments: when the chosen price history is requested and when open or close functions are called.

step2g.png

Then Run (F5) the code again. The debugger stops at the first simulated tick. Now you can look at the “Stream” output window. You can see simulated bid and ask ticks and the report about your strategy execution, where all the strategy alerts and trading operations and their results (long and short amount, floating P/L, and balance) are shown.

step2i.png

Now press F8 to perform the subscription. The request for the data for our subscription appears.

step2j.png

Choose the Quotes Manager and press OK.
Now run the strategy again until another breakpoint (on short or long signal) appears. Then go step-by-step until the CreateOrder command is executed. You see that the short amount appeared in the “streams” window. You can also switch to the “trades” to see the trade table, where a trade appears too. Now the trade will be changed every time a new tick appears to check stop and limit orders (if any are set up) and to recalculate P/L. The “streams” window as well as “accounts” and “summary” tables will be changed accordingly.

Note: there is a small difference with the actual execution of the orders in the Trade Station. While the debugger and back testing reflects the order in the tables immediately, in real life it takes 20-50 milliseconds before the trade is actually executed and up to 500-750 milliseconds while the trade appears in the table. In most cases, it is not important for most of strategies, but never check the trade immediately after the terminal:execute() command. The second difference is that while in real life any order can be filled partially (i.e. can be executed in full but produce a number of trades, or produce a trade for partial amount), the debugger always executes the order in full.

step2k.png

Now you can go either step-by-step (F8) or run between points until the strategy is completely simulated on the chosen data. You can monitor the execution log (“Streams”) as well as the trading tables changes on every step.

When the strategy is finished, the “update is finished” message appears in the output window and the following information is shown:
  • The strategy tick log (shown the Streams tab).
  • The strategy results displayed on the chart (shown on the Chart tab).
  • The strategy statistics (shown on the Statistics tab).
  • The content of all trading tables (Offers, Accounts, Orders, Trades, Closed Trades, and Summary).
Last edited by Anonymous on Thu Dec 22, 2011 7:06 am, edited 2 times in total.
Reason: Updated for the new version of Indicore (2.1)
Nikolay.Gekht
FXCodeBase: Site Admin
 
Posts: 1235
Joined: Wed Dec 16, 2009 6:39 pm
Location: Cary, NC

Step 3. Backtesting

Postby Nikolay.Gekht » Thu Sep 02, 2010 8:59 pm

Step 3. Backtesting

Well, now we’re sure that the strategy works exactly as you wanted. Now you can try it under Trading Station in backtesting mode. The backtesting uses exactly the same simulation routine as the debugger.

So, first install the strategy into Trading Station. The simplest way to do it is just copy the sample.lua file from the “C:\Program Files\Gehtsoft\IndicoreSDK\strategies\” folder into “C:\Program Files\CandleWorks\FXTS2\Strategies\Custom\” folder.

Now, let’s backtest the strategy using default settings (with no stop and limit orders).
To start a new backtest session, go to Marketscope, and then click on "Backtest Strategy" command in "Alerts and Trading Automation" menu.

step3a.png

The backtester window will be promptly opened and a new backtest session wizard is started. On the first page choose the strategy and click Next.

step3b.png

At the next step you can configure the account for backtesting. Fill the options as you wish or leave the default values and click Next.

step3c.png

At the next step you can configure the data and time range for backtesting and the instruments to test the chosen strategy for.

step3d.png


The backtester uses the quote manager server to load the 1-minute quote data for the chosen instrument in chosen period. The quote manager is much faster than the chart server and loading of the whole year of the 1-minute data for an instrument (approx 300K candles) usually takes less than 30 seconds.

You can load the data immediately or choose to load the data before starting the backtester. Let's load the data now.

step3e.png

Click next and wait a bit while the data is loaded.
When data is loaded, the strategy parameters are shown. The page looks very close to the strategy configuration parameters shown when you starts the strategy on the real market. Set the “Time Frame” parameter to "m15" (15 minutes) and leave the strategy parameters unchanged. Click Next.

step3f.PNG


At the last page the MMR (maintenance margin requirement per 1 lot of the contract, expressed in the base account currency) will be shown. Change the margin requirement if necessary and click Start.

step3g.PNG

When backtester is started, the simulation of the market starts. During the simulation, the backtester simulates ticks for each 1-minute candle of chosen instrument(s), activates the chosen strategy (alerts) and simulates the behavior of the orders and trades.

When (and if) backtesting is successfully finished, the following information appears in the backtester window:
  • The chart displaying the whole price history. By default, the timeframe is chosen as the shortest of the timeframes chosen in the strategy parameters. However, you can change it to any time frame in range of 1 minute to 1 month. The tick chart cannot be shown. Also, 15,000 candle limitation does not apply to the backtester charts. For 3-years backtesting you will be able to see all 1,000,000 1-minutes candles on the chart.
  • The equity and balance curves.
  • The information area, which includes:
    o Tick log, where all simulated ticks and actions taken on ticks are listed
    o Statistics, which displays the overall strategy performance
    o Trace log, which can be used to check whether the strategy raised any exception or warnings

step3h.PNG

Well, now make the task a bit harder. Let’s start using the stop and limit orders to manage the risk. In that case, change the backtest parameters the following:

1) Open the backtest parameters (just click the “Restart Backtesting” button on the toolbar. and choose “Change indicator BACKTEST”).
2) Set up the following parameters:
a) The stop to 15 points
b) The limit to 30 points
d) Set the “show alert” to “no” to hide alerts from backtesting.
Click Start.

To find the parameters would be the best for using the strategy, you can optimize it. For details please see Optimizing Strategy Parameters
Last edited by Anonymous on Thu Dec 22, 2011 12:32 pm, edited 1 time in total.
Reason: Updated for the new version of Backtester (November 2011)
Nikolay.Gekht
FXCodeBase: Site Admin
 
Posts: 1235
Joined: Wed Dec 16, 2009 6:39 pm
Location: Cary, NC

Re: Developing, Debugging and Testing your first strategy (upd)

Postby Nikolay.Gekht » Fri Sep 03, 2010 12:39 pm

Update Sep, 04.
The first post was updated. There was a small typo in the final version of the strategy. In the close method the line for NFA regulated accounts must be:

valuemap.Quantity = row.Lot;
instead of
valuemap.Quantity = row.Amount;
I apologize for the inconvenience .
Nikolay.Gekht
FXCodeBase: Site Admin
 
Posts: 1235
Joined: Wed Dec 16, 2009 6:39 pm
Location: Cary, NC

Re: Developing, Debugging and Testing your first strategy (upd)

Postby ecamerin » Mon Jan 31, 2011 2:41 pm

Hi, thank you for the great guide. I'd like to replicate the same procedure in visual studio vb . Can you suggest me a tutorial ?
I saw some sample in C:\Program Files\Gehtsoft\IndicoreDev\samples\vb but i can't fugure out how to perform the entire strategy from scratch to the final test in the same way you did in lua
Thank you
Enrico Italy
ecamerin
 
Posts: 6
Joined: Mon Jan 31, 2011 2:20 pm

Re: Developing, Debugging and Testing your first strategy (upd)

Postby sunshine » Thu Feb 03, 2011 9:09 am

ecamerin wrote:Hi, thank you for the great guide. I'd like to replicate the same procedure in visual studio vb . Can you suggest me a tutorial ?
I saw some sample in C:\Program Files\Gehtsoft\IndicoreDev\samples\vb but i can't fugure out how to perform the entire strategy from scratch to the final test in the same way you did in lua
Thank you
Enrico Italy

Hi,
Are you looking for a way to develop and debug indicators and strategies in VB?
Or are you going to use already created indicator and strategies in Lua in your application?
sunshine
 

Re: Developing, Debugging and Testing your first strategy (upd)

Postby ecamerin » Thu Feb 03, 2011 3:28 pm

The first choice, that is developing and debuging in VB language because i'm familiar with this environment even i don't know the trading method and function. But i hope i can acquire the skill in some tutorial. There are many stuff for mql4 language and Lua but not in Vb.
thank you
ecamerin
 
Posts: 6
Joined: Mon Jan 31, 2011 2:20 pm

Re: Developing, Debugging and Testing your first strategy (upd)

Postby Timon55 » Tue Feb 08, 2011 4:12 am

Hello ecamerin!

IndicoreDev it's an instrument for embedding lua indicators and strategies into your application. So if you simply plan to create your own single strategy, you don't need IndicoreDev.
You can use Order2Go API to develop your strategies in VB(http://forexforums.dailyfx.com/order2go-com-trading-api-support/263465-order2go-beta-build-oct-07-2010-a.html).
Timon55
FXCodeBase: Confirmed User
 
Posts: 43
Joined: Thu Jul 15, 2010 5:35 am
Location: Omsk

Re: Developing, Debugging and Testing your first strategy (upd)

Postby ecamerin » Wed Feb 09, 2011 4:50 am

Thank you for your indication
Enrico
ecamerin
 
Posts: 6
Joined: Mon Jan 31, 2011 2:20 pm

Next

Return to Indicator Development

Who is online

Users browsing this forum: No registered users and 57 guests