FXCM Forex Trading

Strategy modification: OCO + OTO

If you need your own private indicator, signal or strategy, or any kind of development using various trading APIs - please visit this forum.

Moderator: admin

Strategy modification: OCO + OTO

Postby FX.Steady.Trader » 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.

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");
    -- NG: optimizer/backtester hint
    strategy:setTag("NonOptimizableParameters", "ShowAlert,PlaySound,SoundFile,RecurrentSound,SendMail,Email");
    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");


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: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);

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


    if nameOnly then
        return ;

    -- 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");

    if UseMandatoryClosing then
        core.host:execute("setTimer", 1001, math.max(ValidInterval / 2, 1));

function PrepareTrading()
    local PlaySound = instance.parameters.PlaySound;
    if PlaySound then
        SoundFile = instance.parameters.SoundFile;
        SoundFile = nil;
    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;
        Email = nil;
    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);


-- 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

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.

function CheckConditions()
    if not(checkReady("trades")) or not(checkReady("orders")) then
        return ;

    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

            -- we need to create a new trade, do it.
            nextRepeatTime = now + (RepeatDelay / 1440) - (5 / 86400); -- minus one as it takes a second to come back to this method.

    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

        -- check if we are supposed to close open positions first.
        if ClosePositionsOnRepeat then

        -- reset the create flag to create a new trade!
        create = true;

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));

        row = enum:next();

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;

        row = enum:next();

    return found;

-- exit from the specified trade using the direction as a key
function exitNet(BS)
    if not HaveTrades(BS) then
        return ;

    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";
        BuySell = "B";
    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));

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));
               local t = core.parseCsv(msg, ",");
               requestId1 = t[0];
               local second = 1;
               if instance.parameters.SetStop and not(canClose) then
                   second = second + 1;
               if instance.parameters.SetLimit and not(canClose) then
                   second = second + 1;
               requestId2 = t[second];
               --terminal:alertMessage(instance.bid:instrument(), instance.bid[NOW], "orders sent:" .. requestId1 .. "," .. requestId2, instance.bid:date(NOW));
       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;
           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;

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";
       if adistance > 0 then
           valuemap.Rate = instance.ask[NOW] + adistance * pipsize;
           valuemap.Rate = instance.bid[NOW] + adistance * pipsize;
       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;
               valuemap.PegPriceOffsetPipsLimit = -instance.parameters.Limit;
       if instance.parameters.SetStop then
           valuemap.PegTypeStop = "M";
           if aBuySell == "B" then
               valuemap.PegPriceOffsetPipsStop = -instance.parameters.Stop;
               valuemap.PegPriceOffsetPipsStop = instance.parameters.Stop;
               valuemap.TrailStepStop = TrailingStop;

       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";
       return valuemap;

function TerminateAfterTrade()
    if Repeat then
        -- don't abort.

    core.host:execute ("stop");

-- 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");
    elseif cookie == 1111 then
    elseif cookie== 100 then
        if not(success) then
            Signal("create order failed: " .. message);
            Signal("order sent: " .. message);
    elseif cookie== 101 then
        if not(success) then
            Signal("close position failed: " .. message);
    elseif cookie== 301 then
        if not(success) then
            Signal("delete order failed: " .. message);

--                    TRADING UTILITY FUNCTIONS                              --
function Signal(Label)
    if ShowAlert then
        terminal:alertMessage(instance.bid:instrument(), instance.bid[NOW],  Label, instance.bid:date(NOW));

    if SoundFile ~= nil then
        terminal:alertSound(SoundFile, RecurrentSound);

    if Email ~= nil then
        terminal:alertEmail(Email, Label, profile:id() .. "(" .. instance.bid:instrument() .. ")" .. instance.bid[NOW]..", " .. Label..", " .. instance.bid:date(NOW));

function checkReady(table)
    return core.host:execute("isTableFilled", table);

dofile(core.app_path() .. "\\strategies\\standard\\include\\helper.lua");
Posts: 9
Joined: Wed Jul 06, 2016 11:55 am

Re: Strategy modification: OCO + OTO

Postby Georgiy » Tue Sep 27, 2016 6:29 am

Hi FX.Steady.Trader,

Thank you for being interested in our services.
We need one or two days to analyze and estimate the modification. I will get back to you to clarify questions, if any, or to name the price as soon as possible.
FXCodeBase: Initiate
Posts: 151
Joined: Tue Jul 29, 2014 4:49 am

Re: Strategy modification: OCO + OTO

Postby Georgiy » Thu Sep 29, 2016 3:59 am

Hi FX.Steady.Trader,

I have answered via PM.
FXCodeBase: Initiate
Posts: 151
Joined: Tue Jul 29, 2014 4:49 am

Re: Strategy modification: OCO + OTO

Postby ronald3rg » Wed Oct 26, 2016 11:29 pm

Did you get an answer for this I placed a similar order back in June and still have not had it delivered or even started apatently.
Right place at the right time

Posts: 40
Joined: Tue Oct 18, 2011 6:12 pm
Location: New York

Return to Premium Development Services

Who is online

Users browsing this forum: No registered users and 1 guest