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”.
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.
- 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:
- 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");