I'm trying to automate a strategy to put in 2 entry orders and automatically adjust the trading amount depending on the percentage of the account balance you want to risk.
The strategy runs but doesn't trigger and when I change the "amount" to a fixed number, it works.
Any ideas why the strategy won't trigger?
- Code: Select all
--+------------------------------------------------------------------+
--| Entry Order Strategy.lua |
--| Copyright © 2014, Gehtsoft USA LLC |
--| http://fxcodebase.com |
--| Developed by : Mario Jemic |
--| mario.jemic@gmail.com |
--| Enhanced by : MooMooForex |
--| http://moomooforex.com |
--+------------------------------------------------------------------+
function Init() --The strategy profile initialization
strategy:name("Advance Entry Order Strategy");
strategy:description("v4");
-- NG: optimizer/backtester hint
strategy:setTag("NonOptimizableParameters", "ShowAlert,PlaySound,SoundFile,RecurrentSound,SendMail,Email");
strategy:type(core.Both);
strategy.parameters:addGroup("Trading Parameters");
strategy.parameters:addString("Account", "Account to trade", "", "");
strategy.parameters:setFlag("Account", core.FLAG_ACCOUNT);
strategy.parameters:addString("PriceType", "Price Type", "", "Bid");
strategy.parameters:addStringAlternative("PriceType", "Bid", "", "Bid");
strategy.parameters:addStringAlternative("PriceType", "Ask", "", "Ask");
strategy.parameters:addString("TF", "Bar Time frame", "Bar time frame used if Entry Type base is Previous Close.", "D1");
strategy.parameters:setFlag("TF", core.FLAG_PERIODS);
strategy.parameters:addString("BuySell", "Buy or Sell?", "", "B");
strategy.parameters:addStringAlternative("BuySell", "Buy", "", "B");
strategy.parameters:addStringAlternative("BuySell", "Sell", "", "S");
strategy.parameters:addInteger("Distance", "Distance to market (in pips)", "", 9, 0, 1000);
strategy.parameters:addBoolean("SetLimit", "Set Limit Orders", "", true);
strategy.parameters:addBoolean("SetStop", "Set Stop Orders", "", true);
strategy.parameters:addGroup("Risk Management");
strategy.parameters:addDouble("RiskPercentage", "Risk Percentage", "Risk Percentage ", 2,0,100);
strategy.parameters:addString("Type", "Balance/Equity", "", "Balance");
strategy.parameters:addStringAlternative("Type", "Equity", "", "Equity");
strategy.parameters:addStringAlternative("Type", "Balance", "", "Balance");
CreateTradingParameters();
end
function CreateTradingParameters()
strategy.parameters:addGroup("Time Parameters");
strategy.parameters:addString("StartTime", "Start Time for Trading", "", "00:00:00");
strategy.parameters:addString("StopTime", "Stop Time for Trading", "", "24:00:00");
strategy.parameters:addBoolean("UseMandatoryClosing", "Scheduled Terminate", "Terminates the strategy at this time.", false);
strategy.parameters:addString("ExitTime", "Scheduled Termination Time", "", "23:59:00");
strategy.parameters:addInteger("ValidInterval", "Valid interval for operation in second", "", 60);
strategy.parameters:addGroup("Repeat Parameters");
strategy.parameters:addBoolean("Repeat", "Repeat", "Should we repeat this operation periodically. If false, strategy terminates after placing trade.", false);
strategy.parameters:addInteger("RepeatDelay", "Repeat Delay (mins)", "How many minutes should we wait between repeats", 60);
strategy.parameters:addBoolean("CloseOrdersOnRepeat", "Close Orders On Repeat", "Should we close any pending orders when we repeat?", false);
strategy.parameters:addBoolean("ClosePositionsOnRepeat", "Close Positions On Repeat", "Should we close any open positions when we repeat?", false);
strategy.parameters:addString("CustomID", "Identifier", "This identifier is required to identify previously placed orders by this strategy", "EntryOrderStrat");
strategy.parameters:addGroup("Alerts");
strategy.parameters:addBoolean("ShowAlert", "ShowAlert", "", false);
strategy.parameters:addBoolean("PlaySound", "Play Sound", "", false);
strategy.parameters:addFile("SoundFile", "Sound File", "", "");
strategy.parameters:setFlag("SoundFile", core.FLAG_SOUND);
strategy.parameters:addBoolean("RecurrentSound", "Recurrent Sound", "", false);
strategy.parameters:addBoolean("SendEmail", "Send Email", "", false);
strategy.parameters:addString("Email", "Email", "", "");
strategy.parameters:setFlag("Email", core.FLAG_EMAIL);
end
local offer;
local account;
local amount;
local pipsize;
local BuySell;
local distance;
local SoundFile = nil;
local RecurrentSound = false;
local SetLimit;
local Limit;
local SetStop;
local Stop;
local TrailingStop;
local ShowAlert;
local Email;
local SendEmail;
local BaseSize;
local ValidInterval;
local UseMandatoryClosing;
local canClose;
local source = nil;
-- Don't need to store hour + minute + second for each time
local OpenTime, CloseTime, ExitTime;
local Repeat;
local RepeatDelay;
local CloseOrdersOnRepeat;
local ClosePositionsOnRepeat;
local instanceName;
local first;
local RiskPercentage;
local Type;
local PipCost;
local RPC;
local Risk;
local lotSize;
--
function Prepare(nameOnly)
UseMandatoryClosing = instance.parameters.UseMandatoryClosing;
ValidInterval = instance.parameters.ValidInterval;
Repeat = instance.parameters.Repeat;
RepeatDelay = instance.parameters.RepeatDelay;
CloseOrdersOnRepeat = instance.parameters.CloseOrdersOnRepeat;
ClosePositionsOnRepeat = instance.parameters.ClosePositionsOnRepeat;
instanceName = profile:id() .. "( " .. instance.bid:name() .. " )";
instance:name(instanceName);
instanceName = instanceName .. " " .. instance.parameters.CustomID;
Type = instance.parameters.Type;
RiskPercentage = instance.parameters.RiskPercentage;
-- NG: parsing of the time is moved to separate function
local valid;
OpenTime, valid = ParseTime(instance.parameters.StartTime);
assert(valid, "Time " .. instance.parameters.StartTime .. " is invalid");
CloseTime, valid = ParseTime(instance.parameters.StopTime);
assert(valid, "Time " .. instance.parameters.StopTime .. " is invalid");
ExitTime, valid = ParseTime(instance.parameters.ExitTime);
assert(valid, "Time " .. instance.parameters.ExitTime .. " is invalid");
assert(instance.parameters.TF ~= "t1", "The time frame must not be tick");
if nameOnly then
return ;
end
-- this timer will be used to check our conditions every 5 seconds
core.host:execute("setTimer", 1111, 5);
source = ExtSubscribe(1, nil, instance.parameters.TF, instance.parameters.PriceType == "Bid", "bar");
first=source:first();
PipCost = core.host:findTable("offers"):find("Instrument", source:instrument()).PipCost;
local name;
name = profile:id() .. "( " .. instance.bid:name();
local i;
name = name .. " )";
instance:name(name);
if UseMandatoryClosing then
core.host:execute("setTimer", 1001, math.max(ValidInterval / 2, 1));
end
PrepareTrading();
end
function PrepareTrading()
local PlaySound = instance.parameters.PlaySound;
if PlaySound then
SoundFile = instance.parameters.SoundFile;
else
SoundFile = nil;
end
assert(not(PlaySound) or (PlaySound and SoundFile ~= ""), "Sound file must be chosen");
ShowAlert = instance.parameters.ShowAlert;
RecurrentSound = instance.parameters.RecurrentSound;
SendEmail = instance.parameters.SendEmail;
if SendEmail then
Email = instance.parameters.Email;
else
Email = nil;
end
assert(not(SendEmail) or (SendEmail and Email ~= ""), "E-mail address must be specified");
account = instance.parameters.Account;
BuySell = instance.parameters.BuySell;
distance = instance.parameters.Distance;
offer = core.host:findTable("offers"):find("Instrument", instance.bid:instrument()).OfferID;
pipsize = instance.bid:pipSize();
BaseSize = core.host:execute("getTradingProperty", "baseUnitSize", instance.bid:instrument(), account);
SetLimit = instance.parameters.SetLimit;
Limit = instance.parameters.Limit;
SetStop = instance.parameters.SetStop;
Stop = instance.parameters.Stop;
TrailingStop = instance.parameters.TrailingStop;
canClose = core.host:execute("getTradingProperty", "canCreateMarketClose", instance.bid:instrument(), account);
----- risk management
lotSize = core.host:execute("getTradingProperty", "baseUnitSize", instance.bid:instrument(), account);
local accounts = core.host:findTable("accounts");
local enum = accounts:enumerator();
local row = enum:next();
local Balance;
if row ~= nil then
if Type == "Balance" then
Balance= row.Balance;
elseif Type == "Equity" then
Balance= row.Equity;
else
Balance= Equity;
end
end
Risk = (Balance/100)*RiskPercentage;
end
-- NG: create a function to parse time
function ParseTime(time)
local Pos = string.find(time, ":");
local h = tonumber(string.sub(time, 1, Pos - 1));
time = string.sub(time, Pos + 1);
Pos = string.find(time, ":");
local m = tonumber(string.sub(time, 1, Pos - 1));
local s = tonumber(string.sub(time, Pos + 1));
return (h / 24.0 + m / 1440.0 + s / 86400.0), -- time in ole format
((h >= 0 and h < 24 and m >= 0 and m < 60 and s >= 0 and s < 60) or (h == 24 and m == 0 and s == 0)); -- validity flag
end
local create = true;
local nextRepeatTime = nil;
local requestId;
function ExtUpdate(id, source, period) -- The method called every time when a new bid or ask price appears.
end
function CheckConditions()
if not(checkReady("trades")) or not(checkReady("orders")) then
return ;
end
local now = core.host:execute("getServerTime");
local time = now - math.floor(now);
if time >= OpenTime and time <= CloseTime then
if create then
-- confirm we have data from our source;
if not source:hasData(source:size() - 2) then
return;
end
-- we need to create a new trade, do it.
Entry();
nextRepeatTime = now + (RepeatDelay / 1440) - (5 / 86400); -- minus one as it takes a second to come back to this method.
end
end
if not create and Repeat and now >= nextRepeatTime then
-- we need to repeat now
-- check if we are supposed to close pending orders first
if CloseOrdersOnRepeat then
CloseAllOrders();
end
-- check if we are supposed to close open positions first.
if CloseOrdersOnRepeat then
exitNet("B");
exitNet("S");
end
-- reset the create flag to create a new trade!
create = true;
end
end
function CloseAllOrders()
local enum, row;
enum = core.host:findTable("orders"):enumerator();
row = enum:next();
while (row ~= nil) do
if row.AccountID == account and row.OfferID == offer and row.QTXT == instanceName and (row.Type == "LE" or row.Type == "SE") then
-- we want to close the entry orders, not the stop or limit orders etc otherwise we will get errors as they close each other out.
local valuemap = core.valuemap();
valuemap.Command = "DeleteOrder";
valuemap.OrderID = row.OrderID;
success, msg = terminal:execute(301, valuemap);
if not(success) then
terminal:alertMessage(instance.bid:instrument(), instance.bid[NOW], "Failed to delete orders for instance: " .. instanceName .. ": " .. msg, instance.bid:date(NOW));
end
end
row = enum:next();
end
end
function HaveTrades(BuySell)
local enum, row;
local found = false;
enum = core.host:findTable("trades"):enumerator();
row = enum:next();
while (not found) and (row ~= nil) do
if row.AccountID == account and row.OfferID == offer and row.QTXT == instanceName and (row.BS == BuySell or BuySell == nil) then
found = true;
end
row = enum:next();
end
return found;
end
-- exit from the specified trade using the direction as a key
function exitNet(BS)
if not HaveTrades(BS) then
return ;
end
local valuemap, success, msg, BuySell;
valuemap = core.valuemap();
-- switch the direction since the order must be in oppsite direction
local BuySell = nil;
if BS == "B" then
BuySell = "S";
else
BuySell = "B";
end
valuemap.Command = "CreateOrder";
valuemap.OrderType = "CM";
valuemap.OfferID = offer;
valuemap.AcctID = account;
valuemap.NetQtyFlag = "Y"; -- this forces all trades to close in the opposite direction.
valuemap.BuySell = BuySell;
valuemap.CustomID = instanceName;
success, msg = terminal:execute(101, valuemap);
if not(success) then
terminal:alertMessage(instance.bid:instrument(), instance.bid[instance.bid:size() - 1],
"Failed net closing of " .. BS .. " trades for instance " .. instanceName .. ": " .. msg, instance.bid:date(instance.bid:size() - 1));
end
end
function Entry()
create = false;
-- create order
local valuemap = core.valuemap();
valuemap.Command = "CreateOrder";
-- get the price of the previous close.
local prevClose = source.close[source:size() - 2];
local candlesize = math.abs((source.open[source:size() - 2] - source.close[source:size() - 2])/pipsize);
RPC = PipCost * candlesize;
amount = math.floor(Risk/RPC);
-- adjust the rate from there.
if BuySell == "B" then
valuemap.Rate = prevClose + (distance * pipsize);
else
valuemap.Rate = prevClose - (distance * pipsize);
end
-- set the order type
if BuySell == "B" then
if instance.ask[NOW] > valuemap.Rate then
valuemap.OrderType = "LE";
else
valuemap.OrderType = "SE";
end
elseif BuySell == "S" then
if instance.bid[NOW] > valuemap.Rate then
valuemap.OrderType = "SE";
else
valuemap.OrderType = "LE";
end
end
if instance.parameters.SetLimit then
valuemap.PegTypeLimit = "M";
if BuySell == "B" then
valuemap.PegPriceOffsetPipsLimit = candlesize;
else
valuemap.PegPriceOffsetPipsLimit = -candlesize;
end
end
if instance.parameters.SetStop then
valuemap.PegTypeStop = "M";
if BuySell == "B" then
valuemap.PegPriceOffsetPipsStop = -candlesize - distance;
else
valuemap.PegPriceOffsetPipsStop = candlesize + distance;
end
end
if not(canClose) and (instance.parameters.SetStop or instance.parameters.SetLimit) then
-- if regular s/l orders aren't allowed - create ELS order
valuemap.EntryLimitStop = "Y";
end
valuemap.OfferID = offer;
valuemap.AcctID = account;
valuemap.Quantity = amount;
valuemap.BuySell = BuySell;
valuemap.CustomID = instanceName;
core.host:trace("Previous close is " .. prevClose .. " entry price is " .. valuemap.Rate .. " with " .. amount .. " pips ");
local success, msg = terminal:execute(100, valuemap);
if not (success) then
create = true;
else
requestId = core.parseCsv(msg, ",")[0];
Entry2();
end
end
function Entry2()
create = false;
-- create order
local valuemap = core.valuemap();
valuemap.Command = "CreateOrder";
-- get the price of the previous close.
local prevClose = source.close[source:size() - 2];
local candlesize = math.abs((source.open[source:size() - 2] - source.close[source:size() - 2])/pipsize);
RPC = PipCost * candlesize;
amount = math.floor(Risk/RPC);
-- adjust the rate from there.
if BuySell == "B" then
valuemap.Rate = prevClose + (distance * pipsize);
else
valuemap.Rate = prevClose - (distance * pipsize);
end
-- set the order type
if BuySell == "B" then
if instance.ask[NOW] > valuemap.Rate then
valuemap.OrderType = "LE";
else
valuemap.OrderType = "SE";
end
elseif BuySell == "S" then
if instance.bid[NOW] > valuemap.Rate then
valuemap.OrderType = "SE";
else
valuemap.OrderType = "LE";
end
end
if instance.parameters.SetStop then
valuemap.PegTypeStop = "M";
if BuySell == "B" then
valuemap.PegPriceOffsetPipsStop = -candlesize - distance;
else
valuemap.PegPriceOffsetPipsStop = candlesize + distance;
end
end
if not(canClose) and (instance.parameters.SetStop or instance.parameters.SetLimit) then
-- if regular s/l orders aren't allowed - create ELS order
valuemap.EntryLimitStop = "Y";
end
valuemap.OfferID = offer;
valuemap.AcctID = account;
valuemap.Quantity = amount;
valuemap.BuySell = BuySell;
valuemap.CustomID = instanceName;
core.host:trace("Previous close is " .. prevClose .. " entry price is " .. valuemap.Rate .. " with " .. amount .. " pips ");
local success, msg = terminal:execute(100, valuemap);
if not (success) then
create = true;
else
requestId = core.parseCsv(msg, ",")[0];
TerminateAfterTrade();
end
end
function TerminateAfterTrade()
if Repeat then
-- don't abort.
return;
end
core.host:execute ("stop");
end
-- NG: Introduce async function for timer/monitoring for the order results
function ExtAsyncOperationFinished(cookie, success, message)
if cookie == 1001 then
-- timer
if UseMandatoryClosing then
now = core.host:execute("getServerTime");
-- get only time
now = now - math.floor(now);
-- check whether the time is in the exit time period
if now >= ExitTime and now < ExitTime + ValidInterval then
core.host:execute ("stop");
end
end
elseif cookie == 1111 then
CheckConditions();
elseif cookie== 100 then
if not(success) then
Signal("create order failed: " .. message);
else
Signal("order sent: " .. message);
TerminateAfterTrade();
end
elseif cookie== 101 then
if not(success) then
Signal("close position failed: " .. message);
end
elseif cookie== 301 then
if not(success) then
Signal("delete order failed: " .. message);
end
end
end
--===========================================================================--
-- TRADING UTILITY FUNCTIONS --
--============================================================================--
function Signal(Label)
if ShowAlert then
terminal:alertMessage(instance.bid:instrument(), instance.bid[NOW], Label, instance.bid:date(NOW));
end
if SoundFile ~= nil then
terminal:alertSound(SoundFile, RecurrentSound);
end
if Email ~= nil then
terminal:alertEmail(Email, Label, profile:id() .. "(" .. instance.bid:instrument() .. ")" .. instance.bid[NOW]..", " .. Label..", " .. instance.bid:date(NOW));
end
end
function checkReady(table)
return core.host:execute("isTableFilled", table);
end
dofile(core.app_path() .. "\\strategies\\standard\\include\\helper.lua");