-- Id: 14298 --+------------------------------------------------------------------+ --| Copyright © 2016, Gehtsoft USA LLC | --| http://fxcodebase.com | --+------------------------------------------------------------------+ --| Support our efforts by donating | --| Paypal: https://goo.gl/9Rj74e | --| BitCoin : 15VCJTLaz12Amr7adHSBtL9v8XomURo9RF | --+------------------------------------------------------------------+ -- Strategy profile initialization routine -- Defines Strategy profile properties and Strategy parameters function Init() strategy:name("Breakout strategy"); strategy:description("Breakout strategy"); strategy:setTag("NonOptimizableParameters", "Email,SendEmail,SoundFile,RecurrentSound,PlaySound, ShowAlert"); strategy.parameters:addGroup("Price Parameters"); strategy.parameters:addString("TF", "TF", "Time frame ('t1', 'm1', 'm5', etc.)", "m5"); strategy.parameters:setFlag("TF", core.FLAG_PERIODS); strategy.parameters:addGroup("Price"); strategy.parameters:addString("Type", "Price Type", "", "Bid"); strategy.parameters:addStringAlternative("Type", "Bid", "", "Bid"); strategy.parameters:addStringAlternative("Type", "Ask", "", "Ask"); strategy.parameters:addGroup("Strategy Parameters"); strategy.parameters:addString("BreakoutType", "Type of Breakout (PastPeriods or IntraDay)", "No description", "Close"); strategy.parameters:addStringAlternative("BreakoutType", "Close", "Candles closed below/above price.", "Close") strategy.parameters:addStringAlternative("BreakoutType", "Tick", "Current Tick breakout", "Tick") strategy.parameters:addBoolean("OncePerDay", "Once Per Day", "Only one breakout per day allowed.", true) strategy.parameters:addInteger("Threshold", "Excess (in pips) over high limit to open position", "Excess (in pips) over high limit to open position.", 0, 0, 10000) strategy.parameters:addGroup("Breakout Indicator Parameters"); strategy.parameters:addString("PeriodBegin", "The hour and minute when the period begins", "", "00:00"); strategy.parameters:addString("PeriodEnd", "The hour and minute when the period ends", "", "05:30"); strategy.parameters:addString("BoxEnd", "The hour and minute when the box ends", "", "23:00"); strategy.parameters:addString("Type2", "The time type", "", "TD"); strategy.parameters:addStringAlternative("Type2", "Local Time", "", "LT"); strategy.parameters:addStringAlternative("Type2", "EST Time", "", "EST"); strategy.parameters:addStringAlternative("Type2", "GMT Time", "", "GMT"); strategy.parameters:addStringAlternative("Type2", "Trading Day time", "", "TD"); strategy.parameters:addGroup("Trading Parameters"); strategy.parameters:addBoolean("AllowTrade", "Allow strategy to trade", "", false); strategy.parameters:setFlag("AllowTrade", core.FLAG_ALLOW_TRADE); strategy.parameters:addString("Account", "Account to trade on", "", ""); strategy.parameters:setFlag("Account", core.FLAG_ACCOUNT); strategy.parameters:addInteger("Amount", "Trade Amount in Lots", "", 1, 1, 100); strategy.parameters:addInteger("Limit", "Limit Order in pips", "", 30, 1, 10000); strategy.parameters:addBoolean("BreakEven", "Move stop to breakeven level", "", true); strategy.parameters:addDouble("ProfitLevel", "Profit level for breakeven", "", 20); strategy.parameters:addBoolean("TrailingStop", "Trailing stop order", "", false); strategy.parameters:addGroup("Notification"); strategy.parameters:addBoolean("ShowAlert", "Show Alert", "", true); strategy.parameters:addBoolean("PlaySound", "Play Sound", "", false); strategy.parameters:addBoolean("RecurSound", "Recurrent Sound", "", true); strategy.parameters:addFile("SoundFile", "Sound File", "", ""); strategy.parameters:setFlag("SoundFile", core.FLAG_SOUND); strategy.parameters:addBoolean("SendEmail", "Send Email", "", false); strategy.parameters:addString("Email", "Email", "", ""); strategy.parameters:setFlag("Email", core.FLAG_EMAIL); end -- strategy instance initialization routine -- Processes strategy parameters and creates output streams -- TODO: Calculate all constants, create instances all necessary indicators and load all required libraries -- Parameters block local BreakoutType; local Multiple local Threshold -- Notification local PlaySound; local RecurrentSound; local SoundFile; local Email; local SendEmail; -- Trading parameters local AllowTrade; local Account; local Amount; local BaseSize; local Limit; local BreakEven, ProfitLevel; local TrailingStop; local Offer; local CanClose; local minChange; local gSource, gTick; -- streams local P ,H,L local todaySell, todayBuy = false, false -- Routine function Prepare(nameOnly) BreakoutType = instance.parameters.BreakoutType; Threshold = instance.parameters.Threshold * instance.bid:pipSize() Multiple = not instance.parameters.OncePerDay; local name = profile:id() .. "(" .. instance.bid:instrument() .. ")"; instance:name(name); if nameOnly then return ; end ShowAlert = instance.parameters.ShowAlert; PlaySound = instance.parameters.PlaySound; if PlaySound then RecurrentSound = instance.parameters.RecurSound; SoundFile = instance.parameters.SoundFile; else SoundFile = nil; end assert(not(PlaySound) or (PlaySound and SoundFile ~= ""), "Sound file must be specified"); 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"); AllowTrade = instance.parameters.AllowTrade; if AllowTrade then Account = instance.parameters.Account; Amount = instance.parameters.Amount; BaseSize = core.host:execute("getTradingProperty", "baseUnitSize", instance.bid:instrument(), Account); Offer = core.host:findTable("offers"):find("Instrument", instance.bid:instrument()).OfferID; CanClose = core.host:execute("getTradingProperty", "canCreateMarketClose", instance.bid:instrument(), Account); Limit = instance.parameters.Limit * instance.bid:pipSize(); BreakEven = instance.parameters.BreakEven; ProfitLevel = instance.parameters.ProfitLevel; TrailingStop = instance.parameters.TrailingStop; end gTick = ExtSubscribe(1, nil, "t1", instance.parameters.Type == "Bid", "close"); gSource = ExtSubscribe(2, nil, instance.parameters.TF, instance.parameters.Type == "Bid", "bar"); minChange = math.pow(10, -instance.bid:getPrecision()); assert(core.indicators:findIndicator("BREAKOUT") ~= nil, "Please, download and install BREAKOUT.LUA indicator"); I = core.indicators:create("BREAKOUT", gSource, instance.parameters.PeriodBegin, instance.parameters.PeriodEnd, instance.parameters.BoxEnd, instance.parameters.Type2, false); P = I:getStream(0); H = I:getStream(1); L = I:getStream(2); ExtSetupSignal(name .. ':', ShowAlert); ExtSetupSignalMail(name .. ':'); end local BE_NONE = 0; -- belongs to period local BE_PERIOD = 1; -- belongs to period local BE_BOX = 2; -- belongs to box -- strategy calculation routine function ExtUpdate(id, source, period) if AllowTrade then if not(checkReady("trades")) or not(checkReady("orders")) then return ; end end local stream local check = false if id == 2 then I:update(core.UpdateLast); end if (BreakoutType == "Tick" and id == 1) then stream = gTick check = true; elseif (BreakoutType == "Close" and id == 2) then stream = gSource.close check = true; end if BreakEven then MoveStopBE(); end -- the new tick comes if check and P:hasData(NOW) and period >= stream:first() + 1 then local prev, curr = stream[period - 1], stream[period] -- if we're currently in the box if P[NOW] == BE_BOX then if ((prev <= H[NOW] + Threshold) and (curr > H[NOW] + Threshold)) and -- cross high line over threshold tradesCount('B') == 0 and -- no currently open positions (Multiple or (not todayBuy)) then -- first daily trade or multiple daily trades allowed if (AllowTrade) then exit('S') enter('B', L[NOW]) todayBuy = true end if (ShowAlert) then ExtSignal(stream, period, "Price breaks high. Buy.", SoundFile, Email, RecurrentSound); end elseif ((prev >= L[NOW] - Threshold) and (curr < L[NOW] - Threshold)) and tradesCount('S') == 0 and (Multiple or (not todaySell)) then if (AllowTrade) then exit('B') enter('S', H[NOW]) todaySell = true end if (ShowAlert) then ExtSignal(stream, period, "Price breaks low. Sell", SoundFile, Email, RecurrentSound); end end elseif P[NOW] == BE_PERIOD then todaySell, todayBuy = false, false end end end --===========================================================================-- -- TRADING UTILITY FUNCTIONS -- --============================================================================-- function MoveStopBE() if CanClose then local enum = core.host:findTable("trades"):enumerator(); local row = enum:next(); local valuemap; local success, msg; while (row ~= nil) do if row.PL>=ProfitLevel then if row.StopOrderID~=nil then valuemap = core.valuemap(); valuemap.Command = "EditOrder"; valuemap.OrderID = row.StopOrderID; valuemap.AcctID = row.AccountID; valuemap.Rate = row.Open; success, msg = terminal:execute(1000, valuemap); if not success then terminal:alertMessage("", 0, "Failed to move stop: " .. msg, core.now()); end end end row = enum:next(); end else -- for FIFO local order = nil; local rate; local _row; local enum, row; enum = core.host:findTable("orders"):enumerator(); row = enum:next(); while (row ~= nil) do if row.OfferID == Offer and row.BS=="S" and row.Type == "SE" then order = row.OrderID; rate = row.Rate; _row=row; end row = enum:next(); end if order ~= nil then if math.abs(Stop - rate) > minChange then valuemap = core.valuemap(); valuemap.Command = "EditOrder"; valuemap.OfferID = _row.OfferID; valuemap.AcctID = _row.AccountID; valuemap.OrderID = _row.OrderID; valuemap.Rate = _row.Open; executing = true; success, msg = terminal:execute(200, valuemap); if not(success) then executing = false; terminal:alertMessage(instance.bid:instrument(), instance.bid[NOW], "Failed change stop " .. msg, instance.bid:date(NOW)); end end end order = nil; enum = core.host:findTable("orders"):enumerator(); row = enum:next(); while (row ~= nil) do if row.OfferID == Offer and row.BS=="B" and row.Type == "SE" then order = row.OrderID; rate = row.Rate; _row=row; end row = enum:next(); end if order ~= nil then if math.abs(Stop - rate) > minChange then valuemap = core.valuemap(); valuemap.Command = "EditOrder"; valuemap.OfferID = _row.OfferID; valuemap.AcctID = _row.AccountID; valuemap.OrderID = _row.OrderID; valuemap.Rate = _row.Open; executing = true; success, msg = terminal:execute(200, valuemap); if not(success) then executing = false; terminal:alertMessage(instance.bid:instrument(), instance.bid[NOW], "Failed change stop " .. msg, instance.bid:date(NOW)); end end end end end function checkReady(table) local rc; if Account == "TESTACC_ID" then -- run under debugger/simulator rc = true; else rc = core.host:execute("isTableFilled", table); end return rc; end function tradesCount(BuySell) local enum, row; local count = 0; enum = core.host:findTable("trades"):enumerator(); row = enum:next(); while count == 0 and row ~= nil do if row.AccountID == Account and row.OfferID == Offer and (row.BS == BuySell or BuySell == nil) then count = count + 1; end row = enum:next(); end return count end -- enter into the specified direction function enter(BuySell, _Stop) if not(AllowTrade) then return true; end -- do not enter if position in the -- specified direction already exists if tradesCount(BuySell) > 0 then return true; end local valuemap, success, msg; valuemap = core.valuemap(); valuemap.OrderType = "OM"; valuemap.OfferID = Offer; valuemap.AcctID = Account; valuemap.Quantity = Amount * BaseSize; valuemap.BuySell = BuySell; valuemap.PegTypeStop = "M"; if CanClose then -- set limit order if BuySell == "B" then valuemap.RateLimit = instance.ask[NOW] + Limit; else valuemap.RateLimit = instance.bid[NOW] - Limit; end end if CanClose then -- set limit order -- if BuySell == "B" then -- valuemap.RateStop = instance.ask[NOW] - Stop; -- else -- valuemap.RateStop = instance.bid[NOW] + Stop; -- end valuemap.RateStop = _Stop; if TrailingStop then valuemap.TrailStepStop = 1; end end success, msg = terminal:execute(100, valuemap); if not(success) then terminal:alertMessage(instance.bid:instrument(), instance.bid[instance.bid:size() - 1], "Open order failed" .. msg, instance.bid:date(instance.bid:size() - 1)); return false; end if not(CanClose) then if BuySell == "B" then fifoSL("LE", "S", instance.ask[NOW] + Limit, false); else fifoSL("LE", "B", instance.ask[NOW] - Limit, false); end end if not(CanClose) then if BuySell == "B" then -- fifoSL("SE", "S", instance.ask[NOW] - Stop, TrailingStop); fifoSL("SE", "S", _Stop, TrailingStop); else -- fifoSL("SE", "B", instance.ask[NOW] + Stop, TrailingStop); fifoSL("SE", "B", _Stop, TrailingStop); end end return true; end function fifoSL(orderType, side, rate, trailing) local enum, row; local buy, sell; enum = core.host:findTable("orders"):enumerator(); row = enum:next(); while (row ~= nil) do if row.OfferID == Offer and row.AccountID == Account and row.BS == side and row.NetQuantity and row.Type == orderType then return ; end row = enum:next(); end local valuemap, success, msg; valuemap = core.valuemap(); valuemap.Command = "CreateOrder"; valuemap.OrderType = orderType; valuemap.OfferID = Offer; valuemap.AcctID = Account; valuemap.NetQtyFlag = "y"; valuemap.Rate = rate; valuemap.BuySell = side; if trailing then valuemap.TrailUpdatePips = 1; end success, msg = terminal:execute(102, valuemap); if not(success) then terminal:alertMessage(instance.bid:instrument(), instance.bid[NOW], "Failed create limit or stop " .. msg, instance.bid:date(NOW)); end end -- exit from the specified direction function exit(BuySell, use_net) if not (AllowTrade) then return true end if use_net == true then local valuemap, success, msg if tradesCount(BuySell) > 0 then valuemap = core.valuemap() -- switch the direction since the order must be in oppsite direction if BuySell == "B" then BuySell = "S" else BuySell = "B" end valuemap.OrderType = "CM" valuemap.OfferID = Offer valuemap.AcctID = Account valuemap.NetQtyFlag = "Y" valuemap.BuySell = BuySell success, msg = terminal:execute(101, valuemap) if not(success) then terminal:alertMessage( instance.bid:instrument(), instance.bid[instance.bid:size() - 1], "Open order failed"..msg, instance.bid:date(instance.bid:size() - 1) ) return false end return true end else local enum = core.host:findTable("trades"):enumerator(); local row = enum:next(); while row ~= nil do if row.BS == BuySell and row.OfferID == Offer then local valuemap = core.valuemap(); valuemap.BuySell = row.BS == "B" and "S" or "B"; valuemap.OrderType = "CM"; valuemap.OfferID = row.OfferID; valuemap.AcctID = row.AccountID; valuemap.TradeID = row.TradeID; valuemap.Quantity = row.Lot; local success, msg = terminal:execute(101, valuemap); end row = enum: next(); end end return false end dofile(core.app_path() .. "\\strategies\\standard\\include\\helper.lua");