Strategy modification: OCO + OTO
Posted: Mon Sep 26, 2016 11:11 am
Hello, there is a strategy here that I'm already using (see code below), but it's missing something that I'm doing manually, that I would like added to it. This strategy is found on page three of this thread http://fxcodebase.com/code/viewtopic.php?f=31&t=59515&start=20
After the OCO is triggered. I would like it to automatically place a 2nd entry order in the opposite direction of the trade that is executed. I would like to be able to set the following parameters for the 2nd order: distance (from current price), stop, limit, lots.
Example 1: the code below places an OCO at the time I tell it to. Assume the rate moves up, and the buy order is executed (the sell order is canceled because it's an OCO). After the buy order is executed, I would like it to place a sell entry order below the current price using the parameters that I set.
Example 2: the code below places an OCO at the time I tell it to. Assume the rate moves down, and the sell order is executed (the buy order is canceled because it's an OCO). After the sell order is executed, I would like it to place a buy entry order above the current price using the parameters that I set.
Can you provide a quote please? I still want to share it with the community, but I'm interested in possibly paying to have it programmed faster.
Thank you.
After the OCO is triggered. I would like it to automatically place a 2nd entry order in the opposite direction of the trade that is executed. I would like to be able to set the following parameters for the 2nd order: distance (from current price), stop, limit, lots.
Example 1: the code below places an OCO at the time I tell it to. Assume the rate moves up, and the buy order is executed (the sell order is canceled because it's an OCO). After the buy order is executed, I would like it to place a sell entry order below the current price using the parameters that I set.
Example 2: the code below places an OCO at the time I tell it to. Assume the rate moves down, and the sell order is executed (the buy order is canceled because it's an OCO). After the sell order is executed, I would like it to place a buy entry order above the current price using the parameters that I set.
Can you provide a quote please? I still want to share it with the community, but I'm interested in possibly paying to have it programmed faster.
Thank you.
- 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("Entry OCO Strategy");
strategy:description("v1");
-- NG: optimizer/backtester hint
strategy:setTag("NonOptimizableParameters", "ShowAlert,PlaySound,SoundFile,RecurrentSound,SendMail,Email");
strategy:type(core.Both);
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:addGroup("Strategy Parameters");
strategy.parameters:addString("PriceType", "Price Type", "", "Bid");
strategy.parameters:addStringAlternative("PriceType", "Bid", "", "Bid");
strategy.parameters:addStringAlternative("PriceType", "Ask", "", "Ask");
strategy.parameters:addString("EntryType", "Entry Type", "When the time to place the order comes, should the order price be determined from the current price or previous close?", "PreviousClose");
strategy.parameters:addStringAlternative("EntryType", "Current Price", "", "CurrentPrice");
strategy.parameters:addStringAlternative("EntryType", "Previous Close", "", "PreviousClose");
strategy.parameters:addString("TF", "Bar Time frame", "Bar time frame used if Entry Type base is Previous Close.", "m5");
strategy.parameters:setFlag("TF", core.FLAG_PERIODS);
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.", true);
strategy.parameters:addInteger("RepeatDelay", "Repeat Delay (mins)", "How many minutes should we wait between repeats", 5);
strategy.parameters:addBoolean("CloseOrdersOnRepeatCloseOrdersOnRepeat", "Close Orders On Repeat", "Should we close any pending orders when we repeat?", true);
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");
CreateTradingParameters();
end
function CreateTradingParameters()
strategy.parameters:addGroup("Trading Parameters");
strategy.parameters:addString("Account", "Account to trade", "", "");
strategy.parameters:setFlag("Account", core.FLAG_ACCOUNT);
strategy.parameters:addInteger("Amount", "Amount in lots to trade", "", 10, 1, 1000);
strategy.parameters:addInteger("Distance", "Distance to market (in pips)", "", 5, -1000, 1000);
strategy.parameters:addString("BuySell", "Buy or Sell?", "", "B");
strategy.parameters:addStringAlternative("BuySell", "Buy", "", "B");
strategy.parameters:addStringAlternative("BuySell", "Sell", "", "S");
strategy.parameters:addBoolean("SetLimit", "Set Limit Orders", "", false);
strategy.parameters:addInteger("Limit", "Limit Order in pips", "", 5, 1, 10000);
strategy.parameters:addBoolean("SetStop", "Set Stop Orders", "", true);
strategy.parameters:addInteger("Stop", "Stop Order in pips", "", 5, 1, 10000);
strategy.parameters:addInteger("TrailingStop", "Trailing stop order", "Use 0 for none or 1 for dynamic and great than 1 for fixed trailing", 1, 0, 10000);
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;
-- Don't need to store hour + minute + second for each time
local OpenTime, CloseTime, ExitTime;
local source = nil;
local EntryType;
local Repeat;
local RepeatDelay;
local CloseOrdersOnRepeat;
local ClosePositionsOnRepeat;
local instanceName;
--
function Prepare(nameOnly)
UseMandatoryClosing = instance.parameters.UseMandatoryClosing;
ValidInterval = instance.parameters.ValidInterval;
EntryType = instance.parameters.EntryType;
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;
-- 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");
PrepareTrading();
if nameOnly then
return ;
end
-- this timer will be used to check our conditions every 5 seconds
core.host:execute("setTimer", 1111, 5);
if (EntryType == "PreviousClose") then
-- price bid/ask depends on if we are selling or buying.
source = ExtSubscribe(1, nil, instance.parameters.TF, instance.parameters.PriceType == "Bid", "bar");
end
if UseMandatoryClosing then
core.host:execute("setTimer", 1001, math.max(ValidInterval / 2, 1));
end
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;
local lotSize = core.host:execute("getTradingProperty", "baseUnitSize", instance.bid:instrument(), account);
amount = lotSize * instance.parameters.Amount;
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);
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;
local trade = false;
local requestId1;
local requestId2;
function ExtUpdate(id, source, period) -- The method called every time when a new bid or ask price appears.
-- we don't need to do anything here.
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
if (EntryType == "PreviousClose") then
-- confirm we have data from our source;
if not source:hasData(source:size() - 2) then
return;
end
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 ClosePositionsOnRepeat 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()
if create then
create = false;
-- create order
local valuemap = core.valuemap();
valuemap.Command = "CreateOCO";
valuemap:append(CreateOrder("B", distance));
valuemap:append(CreateOrder("S", -distance));
local success, msg = terminal:execute(100, valuemap);
if not (success) then
terminal:alertMessage(instance.bid:instrument(), instance.bid[NOW], "create order failed:" .. msg, instance.bid:date(NOW));
else
local t = core.parseCsv(msg, ",");
requestId1 = t[0];
local second = 1;
if instance.parameters.SetStop and not(canClose) then
second = second + 1;
end
if instance.parameters.SetLimit and not(canClose) then
second = second + 1;
end
requestId2 = t[second];
--terminal:alertMessage(instance.bid:instrument(), instance.bid[NOW], "orders sent:" .. requestId1 .. "," .. requestId2, instance.bid:date(NOW));
end
elseif not(trade) then
-- check whether trade has been created
local row;
row = core.host:findTable("trades"):find("OpenOrderReqID", requestId1);
if row ~= nil then
terminal:alertMessage(instance.bid:instrument(), instance.bid[NOW], "trade created by first order:" .. row.TradeID, instance.bid:date(NOW));
trade = true;
end
local row;
row = core.host:findTable("trades"):find("OpenOrderReqID", requestId2);
if row ~= nil then
terminal:alertMessage(instance.bid:instrument(), instance.bid[NOW], "trade created by second order:" .. row.TradeID, instance.bid:date(NOW));
trade = true;
end
end
end
function CreateOrder(aBuySell, adistance)
local valuemap = core.valuemap();
-- get the order type
if aBuySell == "B" and adistance < 0 then
valuemap.OrderType = "LE";
elseif aBuySell == "B" and adistance > 0 then
valuemap.OrderType = "SE";
elseif aBuySell == "S" and adistance < 0 then
valuemap.OrderType = "SE";
elseif aBuySell == "S" and adistance > 0 then
valuemap.OrderType = "LE";
end
if adistance > 0 then
valuemap.Rate = instance.ask[NOW] + adistance * pipsize;
else
valuemap.Rate = instance.bid[NOW] + adistance * pipsize;
end
valuemap.OfferID = offer;
valuemap.AcctID = account;
valuemap.Quantity = amount;
valuemap.BuySell = aBuySell;
valuemap.CustomID = instanceName;
if instance.parameters.SetLimit then
valuemap.PegTypeLimit = "M";
if aBuySell == "B" then
valuemap.PegPriceOffsetPipsLimit = instance.parameters.Limit;
else
valuemap.PegPriceOffsetPipsLimit = -instance.parameters.Limit;
end
end
if instance.parameters.SetStop then
valuemap.PegTypeStop = "M";
if aBuySell == "B" then
valuemap.PegPriceOffsetPipsStop = -instance.parameters.Stop;
else
valuemap.PegPriceOffsetPipsStop = instance.parameters.Stop;
end
valuemap.TrailStepStop = TrailingStop;
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
return valuemap;
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");