-- More information about this indicator can be found at: -- http://fxcodebase.com/code/viewtopic.php?f=27&t=9222 -- Strategy profile initialization routine -- Defines Strategy profile properties and Strategy parameters function Init() strategy:name("KST Bollinger Strategy") strategy:description("") strategy.parameters:addGroup("Price Parameters") strategy.parameters:addString("TF", "TF", "Time frame ('t1', 'm1', 'm5', etc.)", "m1") strategy.parameters:setFlag("TF", core.FLAG_PERIODS) strategy.parameters:addString("Type", "Bid/Ask", "Bid", "Bid") strategy.parameters:addStringAlternative("Type", "Bid", "", "Bid") strategy.parameters:addStringAlternative("Type", "Ask", "", "Ask") strategy.parameters:addGroup("KST Parameters") AddParam(1, "First", 10, 10) AddParam(2, "Second", 15, 10) AddParam(3, "Third", 20, 10) AddParam(4, "Fourth", 30, 15) AddParam("S", "Signal", nil, 5) strategy.parameters:addGroup("BB Parameters") strategy.parameters:addInteger("N", "Number of periods", "", 20, 1, 10000) strategy.parameters:addDouble("Dev", "Number of standard deviations", "", 1.2, 0.0001, 1000.0) strategy.parameters:addGroup("Trading Parameters") strategy.parameters:addBoolean("AllowTrade", "Allow strategy to trade", "", false) strategy.parameters:addBoolean("AllowMultiple", "Allow Multiple Positions in the same direction", "", true) strategy.parameters:addString("SIDE", "Allow Short/Long/Both Positions", "", "BOTH") strategy.parameters:addStringAlternative("SIDE", "Both", "", "BOTH") strategy.parameters:addStringAlternative("SIDE", "Short", "", "SHORT") strategy.parameters:addStringAlternative("SIDE", "Long", "", "LONG") 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:addBoolean("SetLimit", "Set Limit Orders", "", false) strategy.parameters:addInteger("Limit", "Limit Order in pips", "", 30, 1, 10000) strategy.parameters:addBoolean("SetStop", "Set Stop Orders", "", false) strategy.parameters:addInteger("Stop", "Stop Order in pips", "", 30, 1, 10000) 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 function AddParam(id, name, defROC, defMA) if defROC ~= nil then strategy.parameters:addInteger("ROC" .. id, name .. " ROC Periods", "", defROC, 2, 300) end strategy.parameters:addInteger( "MA" .. id, name .. " MA Periods", "The methods marked with (*) must be downloaded and installed", defMA, 1, 300 ) strategy.parameters:addString("MET" .. id, name .. " Method", "", "EMA") strategy.parameters:addStringAlternative("MET" .. id, "EMA", "", "EMA") strategy.parameters:addStringAlternative("MET" .. id, "MVA", "", "MVA") strategy.parameters:addStringAlternative("MET" .. id, "LWMA", "", "LWMA") strategy.parameters:addStringAlternative("MET" .. id, "TMA", "", "TMA") strategy.parameters:addStringAlternative("MET" .. id, "SMMA(*)", "", "SMMA") strategy.parameters:addStringAlternative("MET" .. id, "Vidya (1995)*", "", "VIDYA") strategy.parameters:addStringAlternative("MET" .. id, "Vidya (1992)*", "", "VIDYA92") strategy.parameters:addStringAlternative("MET" .. id, "Wilders*", "", "WMA") end -- Parameters block local SIDE local gSource = nil -- the source stream local PlaySound local RecurrentSound local SoundFile local Email local SendEmail local AllowTrade local Account local Amount local BaseSize local SetLimit local Limit local SetStop local Stop local TrailingStop local Offer local CanClose local AllowMultiple local ShowAlert --TODO: Add variable(s) for your strategy if needed local indicator = {} local first = 1 -- strategy instance initialization routine -- Processes strategy parameters and subscribe to price streams -- TODO: Calculate all constants, create instances all necessary indicators and load all required libraries function Prepare(nameOnly) SIDE = instance.parameters.SIDE AllowMultiple = instance.parameters.AllowMultiple SC = instance.parameters.SC SH = instance.parameters.SH SL = instance.parameters.SL FC = instance.parameters.FC local name = profile:id() .. "(" .. instance.bid:instrument() .. ")" instance:name(name) assert(instance.parameters.TF ~= "t1", "The strategy cannot be applied on ticks.") ShowAlert = instance.parameters.ShowAlert if ShowAlert then 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") end assert(core.indicators:findIndicator("KST") ~= nil, "Please download and install KST Indicator!") 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) SetLimit = instance.parameters.SetLimit Limit = instance.parameters.Limit * instance.bid:pipSize() SetStop = instance.parameters.SetStop Stop = instance.parameters.Stop * instance.bid:pipSize() TrailingStop = instance.parameters.TrailingStop end --TODO: Find indicator's profile, intialize parameters, and create indicator's instance (if needed) gSource = ExtSubscribe(1, nil, instance.parameters.TF, instance.parameters.Type == "Bid", "bar") local iprofile = core.indicators:findIndicator("KST") local iparams = iprofile:parameters() local i for i = 1, 4, 1 do if i ~= 4 then iparams:setInteger("ROC" .. i, instance.parameters:getInteger("ROC" .. i)) end iparams:setInteger("MA" .. i, instance.parameters:getInteger("MA" .. i)) iparams:setString("MET" .. i, instance.parameters:getString("MET" .. i)) end indicator[1] = iprofile:createInstance(gSource.close, iparams) local iprofile2 = core.indicators:findIndicator("BB") local iparams2 = iprofile2:parameters() iparams2:setInteger("N", instance.parameters:getInteger("N")) iparams2:setDouble("Dev", instance.parameters:getDouble("Dev")) indicator[2] = iprofile2:createInstance(indicator[1].KST, iparams2) first = math.max(indicator[1].DATA:first(), indicator[2].DATA:first()) setTimer() if nameOnly then return end end -- strategy calculation routine -- TODO: Add your code for decision making -- TODO: Update the instance of your indicator(s) if needed function ExtUpdate(id, source, period) if period < first + 1 or id ~= 1 then return end indicator[1]:update(core.UpdateLast) indicator[2]:update(core.UpdateLast) if not indicator[1].KST:hasData(period - 1) or not indicator[2].TL:hasData(period - 1) then return end if core.crossesOver(indicator[1].KST, indicator[2].TL, period) then if haveTrades("B") and not AllowMultiple then return end if SIDE == "SHORT" then return end if ShowAlert then terminal:alertMessage(instance.bid:instrument(), instance.bid[NOW], "Enter Long", instance.bid:date(NOW)) end if SoundFile ~= nil then terminal:alertSound(SoundFile, RecurrentSound) end if Email ~= nil then terminal:alertEmail( Email, "Enter Long", profile:id() .. "(" .. instance.bid:instrument() .. ")" .. instance.bid[NOW] .. ", " .. "Enter Long" .. ", " .. instance.bid:date(NOW) ) end if AllowTrade then if haveTrades("S") then exit("S") end enter("B") end elseif core.crossesUnder(indicator[1].KST, indicator[2].BL, period) then if haveTrades("S") and not AllowMultiple then return end if SIDE == "LONG" then return end if ShowAlert then terminal:alertMessage(instance.bid:instrument(), instance.bid[NOW], "Enter Short", instance.bid:date(NOW)) end if SoundFile ~= nil then terminal:alertSound(SoundFile, RecurrentSound) end if Email ~= nil then terminal:alertEmail( Email, "Enter Short", profile:id() .. "(" .. instance.bid:instrument() .. ")" .. ", " .. instance.bid[NOW] .. ", " .. "Enter Short" .. ", " .. instance.bid:date(NOW) ) end if AllowTrade then if haveTrades("B") then exit("B") end enter("S") end end end function ExtAsyncOperationFinished(id, success, message) checkTimer(id, success, message) end --===========================================================================-- -- TRADING UTILITY FUNCTIONS -- --============================================================================-- local TRADE_CHECK = 10001 local requestId = nil local limitInfo, stopInfo -- ----------------------------------------------------------------------- -- Function checks that specified table is ready (filled) for processing -- or that we running under debugger/simulation conditions. -- ----------------------------------------------------------------------- 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 -- ----------------------------------------------------------------------- -- Return count of opened trades for spicific direction -- (both directions if BuySell parameters is 'nil') -- ----------------------------------------------------------------------- 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.BS == BuySell or BuySell == nil) then found = true end row = enum:next() end return found end -- ----------------------------------------------------------------------- -- Sets timer for FIFO accounts which is check s -- ----------------------------------------------------------------------- function setTimer() -- Set check timer for FIFO accounts only if AllowTrade and not CanClose then core.host:execute("setTimer", TRADE_CHECK, 1) end end -- ----------------------------------------------------------------------- -- Checks necessity to create stop/limit orders -- ----------------------------------------------------------------------- function checkTimer(id, success, message) if ((not CanClose) and id == TRADE_CHECK and requestId ~= nil) then local trades = core.host:findTable("trades") row = trades:find("OpenOrderReqID", requestId) if (row ~= nil) then local stopped = false if SetLimit then if (limitInfo.SB == "S" and instance.bid[NOW] >= limitInfo.rate) or (limitInfo.SB == "B" and instance.ask[NOW] <= limitInfo.rate) then exit(row.BS) stopped = true end end if SetStop and (not stopped) then if (stopInfo.SB == "S" and instance.bid[NOW] <= stopInfo.rate) or (stopInfo.SB == "B" and instance.ask[NOW] >= stopInfo.rate) then exit(row.BS) stopped = true end end if (not stopped) then if SetLimit then fifoSL("LE", limitInfo.SB, limitInfo.rate, false) end if SetStop then fifoSL("SE", stopInfo.SB, stopInfo.rate, stopInfo.trail) end end requestId = nil end end end -- ----------------------------------------------------------------------- -- Enter into the specified direction -- BuySell : direction to enter, shoulde be 'B' for long position or 'S' for short -- ----------------------------------------------------------------------- function enter(BuySell) if not (AllowTrade) then return true end local valuemap, success, msg -- do not enter if position in the specified direction already exists if not AllowMultiple then if haveTrades(BuySell) then return true end end valuemap = core.valuemap() valuemap.OrderType = "OM" valuemap.OfferID = Offer valuemap.AcctID = Account valuemap.Quantity = Amount * BaseSize valuemap.BuySell = BuySell valuemap.PegTypeStop = "M" if SetLimit and 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 SetStop and CanClose then -- set limit order if BuySell == "B" then valuemap.RateStop = instance.ask[NOW] - Stop else valuemap.RateStop = instance.bid[NOW] + Stop end 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 SetLimit and not (CanClose) then requestId = msg if BuySell == "B" then limitInfo = {SB = "S", rate = instance.ask[NOW] + Limit} else limitInfo = {SB = "B", rate = instance.bid[NOW] - Limit} end end if SetStop and not (CanClose) then requestId = msg if BuySell == "B" then stopInfo = {SB = "S", rate = instance.ask[NOW] - Stop, trail = TrailingStop} else stopInfo = {SB = "B", rate = instance.bid[NOW] + Stop, trail = TrailingStop} end end return true end -- ----------------------------------------------------------------------- -- Fifo Stop/Limit helper. This function creates -- BuySell : direction to enter, shoulde be 'B' for long position or 'S' for short -- ----------------------------------------------------------------------- function fifoSL(orderType, side, rate, trailing) local valuemap = core.valuemap() valuemap.Command = "CreateOrder" 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 valuemap.Command = "EditOrder" valuemap.OrderID = row.OrderID break end row = enum:next() end 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 local success, msg 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) if not (AllowTrade) then return end local valuemap, success, msg if haveTrades(BuySell) 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) ) end end end --===========================================================================-- -- END OF TRADING UTILITY FUNCTIONS -- --===========================================================================-- dofile(core.app_path() .. "\\strategies\\standard\\include\\helper.lua")