-- Id: 22643 -- More information about this indicator can be found at: -- http://fxcodebase.com/code/viewtopic.php?f=31&t=65964 --+------------------------------------------------------------------+ --| Copyright © 2018, Gehtsoft USA LLC | --| http://fxcodebase.com | --+------------------------------------------------------------------+ --| Developed by : Mario Jemic | --| mario.jemic@gmail.com | --+------------------------------------------------------------------+ --| Support our efforts by donating | --| Patreon : https://goo.gl/GdXWeN | --| Paypal : https://goo.gl/9Rj74e | --| BitCoin : 15VCJTLaz12Amr7adHSBtL9v8XomURo9RF | --| BitCoin Cash : 1BEtS465S3Su438Kc58h2sqvVvHK9Mijtg | --| Ethereum : 0x8C110cD61538fb6d7A2B47858F0c0AaBd663068D | --| LiteCoin : LLU8PSY2vsq7B9kRELLZQcKf5nJQrdeqwD | --+------------------------------------------------------------------+ local Modules = {}; function Init() --The strategy profile initialization strategy:name("Quantum Donchian Channel Strategy") strategy:description("") strategy:setTag("NonOptimizableParameters", "Email,SendEmail,SoundFile,RecurrentSound,PlaySound, ShowAlert") 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:addString("TF", "Time frame", "", "m1") strategy.parameters:setFlag("TF", core.FLAG_PERIODS) strategy.parameters:addGroup("Donchian Channel Calculation") strategy.parameters:addInteger("N", "Number of periods", "", 20, 2, 10000) strategy.parameters:addGroup("Quantum Calculation") strategy.parameters:addInteger("Period", "Period", "Period", 300) strategy.parameters:addBoolean("ReversalOnly", "Trend reversal only", "", true) strategy.parameters:addString("ExitType", "ExitType", "", "Central") strategy.parameters:addStringAlternative("ExitType", "Central", "", "Central") strategy.parameters:addStringAlternative("ExitType", "Top/Bottom", "", "TopBottom") CreateTradingParameters() end function CreateTradingParameters() strategy.parameters:addGroup("Execution Parameters") strategy.parameters:addBoolean("AllowTrade", "Allow strategy to trade", "", true) strategy.parameters:setFlag("AllowTrade", core.FLAG_ALLOW_TRADE) strategy.parameters:addString("AccountType", "Account Type", "", "Automatic") strategy.parameters:addStringAlternative("AccountType", "FIFO", "", "FIFO") strategy.parameters:addStringAlternative("AccountType", "non FIFO", "", "NON") strategy.parameters:addStringAlternative("AccountType", "Automatic", "", "Automatic") strategy.parameters:addString("EntryExecutionType", "Entry Execution Type", "", "EndOfTurn") strategy.parameters:addStringAlternative("EntryExecutionType", "End of Turn", "", "EndOfTurn") strategy.parameters:addStringAlternative("EntryExecutionType", "Live", "", "Live") --********************************************************************************************************* strategy.parameters:addString("ExitExecutionType", "Exit Execution Type", "", "EndOfTurn") strategy.parameters:addStringAlternative("ExitExecutionType", "End of Turn", "", "EndOfTurn") strategy.parameters:addStringAlternative("ExitExecutionType", "Live", "", "Live") --********************************************************************************************************* strategy.parameters:addGroup("Trade Parameters") strategy.parameters:addBoolean("CloseOnOpposite", "Close On Opposite", "", true) strategy.parameters:addString( "CustomID", "Custom Identifier", "The identifier that can be used to distinguish strategy instances", "QDCS" ) strategy.parameters:addBoolean("PositionCap", "Use Position Cap", "", false) strategy.parameters:addInteger( "MaxNumberOfPositionInAnyDirection", "Max Number Of Open Position In Any Direction", "", 2 ) strategy.parameters:addInteger("MaxNumberOfPosition", "Max Number Of Position In One Direction", "", 1) strategy.parameters:addString( "ALLOWEDSIDE", "Allowed side", "Allowed side for trading or signaling, can be Sell, Buy or Both", "Both" ) strategy.parameters:addStringAlternative("ALLOWEDSIDE", "Both", "", "Both") strategy.parameters:addStringAlternative("ALLOWEDSIDE", "Buy", "", "Buy") strategy.parameters:addStringAlternative("ALLOWEDSIDE", "Sell", "", "Sell") strategy.parameters:addString("Direction", "Type of Signal / Trade", "", "direct") strategy.parameters:addStringAlternative("Direction", "Direct", "", "direct") strategy.parameters:addStringAlternative("Direction", "Reverse", "", "reverse") strategy.parameters:addString("Account", "Account to trade on", "", "") strategy.parameters:setFlag("Account", core.FLAG_ACCOUNT) strategy.parameters:addInteger("Amount", "Trade Amount in Lots", "", 1) strategy.parameters:addBoolean("SetLimit", "Set Limit Orders", "", false) strategy.parameters:addInteger("Limit", "Limit Order in pips", "", 30) strategy.parameters:addBoolean("SetStop", "Set Stop Orders", "", false) strategy.parameters:addInteger("Stop", "Stop Order in pips", "", 30) strategy.parameters:addBoolean("TrailingStop", "Trailing stop order", "", false) --********************************************************************************************************* strategy.parameters:addBoolean("Exit", "Use Optional Exit", "", true) --********************************************************************************************************* strategy.parameters:addGroup("Alerts") signaler:Init(strategy.parameters); strategy.parameters:addGroup("Time Parameters") strategy.parameters:addInteger("ToTime", "Convert the date to", "", 6) strategy.parameters:addIntegerAlternative("ToTime", "EST", "", 1) strategy.parameters:addIntegerAlternative("ToTime", "UTC", "", 2) strategy.parameters:addIntegerAlternative("ToTime", "Local", "", 3) strategy.parameters:addIntegerAlternative("ToTime", "Server", "", 4) strategy.parameters:addIntegerAlternative("ToTime", "Financial", "", 5) strategy.parameters:addIntegerAlternative("ToTime", "Display", "", 6) 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("ManageExit", "Use Exit after Stop Time", "", true) --********************************************************************************************************* strategy.parameters:addBoolean("UseMandatoryClosing", "Use Mandatory Closing", "", false) strategy.parameters:addString("ExitTime", "Mandatory Closing Time", "", "23:59:00") strategy.parameters:addInteger("ValidInterval", "Valid interval for operation in second", "", 60) end local AccountType local Source, TickSource local MaxNumberOfPositionInAnyDirection, MaxNumberOfPosition local ALLOWEDSIDE local AllowTrade local Offer local CanClose local Account local Amount local SetLimit local Limit local SetStop local Stop local TrailingStop local BaseSize local EntyExecutionType, ExitExecutionType local CloseOnOpposite local first local Direction local CustomID local PositionCap local TF local OpenTime, CloseTime, ExitTime local LastEntry, LastExit local ToTime local ValidInterval, UseMandatoryClosing --********************************************************************************************************* local ManageExit, Exit --********************************************************************************************************* --Indicator parameters local DNC, N local Quantum, Period, ReversalOnly local ExitType function Prepare(nameOnly) for _, module in pairs(Modules) do module:Prepare(nameOnly); end CustomID = instance.parameters.CustomID name = profile:id() .. ", " .. instance.bid:name() .. ", " .. CustomID instance:name(name) if nameOnly then return end ExitType = instance.parameters.ExitType AccountType = instance.parameters.AccountType EntryExecutionType = instance.parameters.EntryExecutionType ExitExecutionType = instance.parameters.ExitExecutionType CloseOnOpposite = instance.parameters.CloseOnOpposite MaxNumberOfPositionInAnyDirection = instance.parameters.MaxNumberOfPositionInAnyDirection MaxNumberOfPosition = instance.parameters.MaxNumberOfPosition Direction = instance.parameters.Direction == "direct" TF = instance.parameters.TF ToTime = instance.parameters.ToTime if ToTime == 1 then ToTime = core.TZ_EST elseif ToTime == 2 then ToTime = core.TZ_UTC elseif ToTime == 3 then ToTime = core.TZ_LOCAL elseif ToTime == 4 then ToTime = core.TZ_SERVER elseif ToTime == 5 then ToTime = core.TZ_FINANCIAL elseif ToTime == 6 then ToTime = core.TZ_TS end PositionCap = instance.parameters.PositionCap ValidInterval = instance.parameters.ValidInterval UseMandatoryClosing = instance.parameters.UseMandatoryClosing LastEntry = nil LastExit = nil --********************************************************************************************************* ManageExit = instance.parameters.ManageExit Exit = instance.parameters.Exit --********************************************************************************************************* --Indicator parameters N = instance.parameters.N Period = instance.parameters.Period ReversalOnly = instance.parameters.ReversalOnly assert(TF ~= "t1", "The time frame must not be tick") PrepareTrading() assert(core.indicators:findIndicator("DNC") ~= nil, "Please, download and install DNC.LUA indicator") assert(core.indicators:findIndicator("QUANTUM") ~= nil, "Please, download and install QUANTUM.LUA indicator") if EntryExecutionType == "Live" or --**************************************************************************************************** ExitExecutionType == "Live" then --****************************************************************************************************** TickSource = ExtSubscribe(1, nil, "t1", instance.parameters.Type == "Bid", "close") end Source = ExtSubscribe(2, nil, TF, instance.parameters.Type == "Bid", "bar") DNC = core.indicators:create("DNC", Source, N, "yes", "yes", false) Quantum = core.indicators:create("QUANTUM", Source, Period, ReversalOnly, true) first = math.max(DNC.DM:first(), Period) ValidInterval = instance.parameters.ValidInterval UseMandatoryClosing = instance.parameters.UseMandatoryClosing 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") if UseMandatoryClosing then core.host:execute("setTimer", 100, math.max(ValidInterval / 2, 1)) end end function ReleaseInstance() for _, module in pairs(Modules) do if module.ReleaseInstance ~= nil then module:ReleaseInstance(); end end core.host:execute("killTimer", 100) end -- NG: create a function to parse time function ParseTime(time) local Pos = string.find(time, ":") if Pos == nil then return nil, false end local h = tonumber(string.sub(time, 1, Pos - 1)) time = string.sub(time, Pos + 1) Pos = string.find(time, ":") if Pos == nil then return nil, false end 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), ((h >= 0 and h < 24 and m >= 0 and m < 60 and s >= 0 and s < 60) or -- time in ole format (h == 24 and m == 0 and s == 0)) -- validity flag end function PrepareTrading() ALLOWEDSIDE = instance.parameters.ALLOWEDSIDE AllowTrade = instance.parameters.AllowTrade 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); if AccountType == "FIFO" then CanClose = false elseif AccountType == "NON" then CanClose = true else CanClose = core.host:execute("getTradingProperty", "canCreateMarketClose", instance.bid:instrument(), Account) end SetLimit = instance.parameters.SetLimit Limit = instance.parameters.Limit SetStop = instance.parameters.SetStop Stop = instance.parameters.Stop TrailingStop = instance.parameters.TrailingStop end function ExtUpdate(id, source, period) -- The method called every time when a new bid or ask price appears. for _, module in pairs(Modules) do if module.BlockTrading ~= nil and module:BlockTrading(id, source, period) then return; end end for _, module in pairs(Modules) do if module.ExtUpdate ~= nil then module:ExtUpdate(id, source, period); end end if AllowTrade then if not (checkReady("trades")) or not (checkReady("orders")) then return end end if period < 0 then return end if EntryExecutionType == "Live" or --**************************************************************************************************** ExitExecutionType == "Live" then --**************************************************************************************************** if id ~= 1 then return end period = core.findDate(Source, TickSource:date(period), false) else if id ~= 2 then return end end now = core.host:execute("getServerTime") now = core.host:execute("convertTime", core.TZ_EST, ToTime, now) -- get only time now = now - math.floor(now) -- update indicators. DNC:update(core.UpdateLast) Quantum:update(core.UpdateLast) if not Source.close:hasData(period) or period < first then return end if EntryExecutionType == "Live" and id == 1 or EntryExecutionType ~= "Live" and id ~= 1 then EntryFunction(now, period) end if ExitExecutionType == "Live" and id == 1 or ExitExecutionType ~= "Live" and id ~= 1 then ExitFunction(now, period) end end function ExitFunction(now, period) if not Exit then return end if not InRange(now, OpenTime, CloseTime) and not ManageExit then return end if (LastExit == Source:serial(period)) then return end if (Source.close[period] > DNC.DM[period] and ExitType == "Central") or (Source.close[period] > DNC.DU[period] and ExitType ~= "Central") then if Direction then if haveTrades("B") then exitSpecific("B") Signal("Close Long") end else if haveTrades("S") then exitSpecific("S") Signal("Close Short") end end LastExit = Source:serial(period) end if (Source.close[period] < DNC.DM[period] and ExitType == "Central") or (Source.close[period] < DNC.DN[period] and ExitType ~= "Central") then if Direction then if haveTrades("S") then exitSpecific("S") Signal("Close Short") end else if haveTrades("B") then exitSpecific("B") Signal("Close Long") end end LastExit = Source:serial(period) end end function InRange(now, openTime, closeTime) if openTime < closeTime then return now >= openTime and now <= closeTime; end if openTime > closeTime then return now > openTime or now < closeTime; end return now == openTime; end local last_buy_high; local last_sell_low; function EntryFunction(now, period) if not InRange(now, OpenTime, CloseTime) then return false; end if (LastEntry == Source:serial(period)) then return false; end -- only buy if we have a fast cross over slow and the price is above the moving averages. if (Source.close[period] < DNC.DM[period] and Quantum.Signal[period] == -1) or (haveTrades("B") and Source.close[period] > last_buy_high) then if Direction then BUY() else SELL() end last_buy_high = Source.high[period]; LastEntry = Source:serial(period) return true; elseif (Source.close[period] > DNC.DM[period] and Quantum.Signal[period] == 1) or (haveTrades("S") and Source.close[period] < last_sell_low) then if Direction then SELL() else BUY() end last_sell_low = Source.low[period]; LastEntry = Source:serial(period) return true; end return false; end -- NG: Introduce async function for timer/monitoring for the order results function ExtAsyncOperationFinished(cookie, success, message) for _, module in pairs(Modules) do if module.AsyncOperationFinished ~= nil then module:AsyncOperationFinished(cookie, success, message, message1, message2); end end if cookie == 100 then -- timer if UseMandatoryClosing and AllowTrade then now = core.host:execute("getServerTime") now = core.host:execute("convertTime", core.TZ_EST, ToTime, now) -- 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 / 86400.0) then if not checkReady("trades") then return end if haveTrades("B") then exitSpecific("B") Signal("Close Long") end if haveTrades("S") then exitSpecific("S") Signal("Close Short") end end end elseif cookie == 200 and not success then terminal:alertMessage( instance.bid:instrument(), instance.bid[instance.bid:size() - 1], "Open order failed" .. message, instance.bid:date(instance.bid:size() - 1) ) elseif cookie == 201 and not success then terminal:alertMessage( instance.bid:instrument(), instance.bid[instance.bid:size() - 1], "Close order failed" .. message, instance.bid:date(instance.bid:size() - 1) ) end end --===========================================================================-- -- TRADING UTILITY FUNCTIONS -- --============================================================================-- function BUY() if AllowTrade then --if CanClose and CloseOnOpposite and haveTrades("S") then if (CloseOnOpposite or Hedge) and haveTrades("S") then -- close on opposite signal exitSpecific("S") Signal("Close Short") end if ALLOWEDSIDE == "Sell" then -- we are not allowed buys. return end enter("B", 0) else Signal("Buy Signal") end end function HEDGELONG() if ALLOWEDSIDE == "Buy" and haveTrades("B") then -- we are not allowed sells. return end if not haveTrades("B") then return end if AllowTrade then local bCount = tradesCount("B") if bCount > 0 then exitSpecific("B") Signal("Hedge Long") enter("S", bCount) end else Signal("Hedge Long") end end function HEDGESHORT() if ALLOWEDSIDE == "Sell" and haveTrades("S") then -- we are not allowed buys. return end if not haveTrades("S") then return end if AllowTrade then local sCount = tradesCount("S") if sCount > 0 then exitSpecific("S") Signal("Hedge Short") enter("B", sCount) end else Signal("Hedge Short") end end function SELL() if AllowTrade then --if CanClose and CloseOnOpposite and haveTrades("B") then if (CloseOnOpposite or Hedge) and haveTrades("B") then -- close on opposite signal exitSpecific("B") Signal("Close Long") end if ALLOWEDSIDE == "Buy" then -- we are not allowed sells. return end enter("S", 0) else Signal("Sell Signal") end end function Signal(Label) signaler:Signal(Label, instance.bid); 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 row ~= nil do if row.AccountID == Account and row.OfferID == Offer and row.QTXT == CustomID and (row.BS == BuySell or BuySell == nil) then count = count + 1 end row = enum:next() end return count end function haveTrades(BuySell) local enum, row local found = false enum = core.host:findTable("trades"):enumerator() row = enum:next() while (row ~= nil) do if row.AccountID == Account and row.OfferID == Offer and row.QTXT == CustomID and (row.BS == BuySell or BuySell == nil) then found = true break end row = enum:next() end return found end -- enter into the specified direction function enter(BuySell, hCount) -- do not enter if position in the specified direction already exists if (tradesCount(BuySell) >= MaxNumberOfPosition or (tradesCount(nil) >= MaxNumberOfPositionInAnyDirection)) and PositionCap then return true end -- send the alert after the checks to see if we can trade. if (BuySell == "S") then Signal("Sell Signal") else Signal("Buy Signal") end return MarketOrder(BuySell, hCount) end -- enter into the specified direction function MarketOrder(BuySell, hCount) -- if trade_in_progress then --return; --end -- trade_in_progress=true; local valuemap, success, msg valuemap = core.valuemap() valuemap.Command = "CreateOrder" valuemap.OrderType = "OM" valuemap.OfferID = Offer valuemap.AcctID = Account if hCount > 0 then valuemap.Quantity = hCount * BaseSize else valuemap.Quantity = Amount * BaseSize end valuemap.BuySell = BuySell valuemap.CustomID = CustomID -- add stop/limit valuemap.PegTypeStop = "O" if SetStop then if BuySell == "B" then valuemap.PegPriceOffsetPipsStop = -Stop else valuemap.PegPriceOffsetPipsStop = Stop end end if TrailingStop then valuemap.TrailStepStop = 1 end valuemap.PegTypeLimit = "O" if SetLimit then if BuySell == "B" then valuemap.PegPriceOffsetPipsLimit = Limit else valuemap.PegPriceOffsetPipsLimit = -Limit end end if (not CanClose) then valuemap.EntryLimitStop = "Y" end success, msg = terminal:execute(200, 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 function exitSpecific(BuySell) if not AllowTrade then return end --side -- closes all positions of the specified direction (B for buy, S for sell) local enum, row, valuemap enum = core.host:findTable("trades"):enumerator() while true do row = enum:next() if row == nil then break end if row.AccountID == Account and row.OfferID == Offer and row.BS == BuySell and row.QTXT == CustomID then -- if trade has to be closed if CanClose then -- non-FIFO account, create a close market order valuemap = core.valuemap() valuemap.OrderType = "CM" valuemap.OfferID = Offer valuemap.AcctID = Account valuemap.Quantity = row.Lot valuemap.TradeID = row.TradeID valuemap.CustomID = CustomID if row.BS == "B" then valuemap.BuySell = "S" else valuemap.BuySell = "B" end success, msg = terminal:execute(201, valuemap) if not (success) then terminal:alertMessage( instance.bid:instrument(), instance.bid[instance.bid:size() - 1], "Close order failed" .. msg, instance.bid:date(instance.bid:size() - 1) ) return false end else -- FIFO account, create an opposite market order valuemap = core.valuemap() valuemap.OrderType = "OM" valuemap.OfferID = Offer valuemap.AcctID = Account --valuemap.Quantity = Amount*BaseSize; valuemap.Quantity = row.Lot valuemap.CustomID = CustomID if row.BS == "B" then valuemap.BuySell = "S" else valuemap.BuySell = "B" end success, msg = terminal:execute(201, valuemap) if not (success) then terminal:alertMessage( instance.bid:instrument(), instance.bid[instance.bid:size() - 1], "Close order failed" .. msg, instance.bid:date(instance.bid:size() - 1) ) return false end end end end end dofile(core.app_path() .. "\\strategies\\standard\\include\\helper.lua") signaler = {}; signaler.Name = "Signaler"; signaler.Debug = false; signaler.Version = "1.2.1"; signaler._show_alert = nil; signaler._sound_file = nil; signaler._recurrent_sound = nil; signaler._email = nil; signaler._ids_start = nil; signaler._telegram_timer = nil; signaler._tz = nil; signaler._alerts = {}; function signaler:trace(str) if not self.Debug then return; end core.host:trace(self.Name .. ": " .. str); end function signaler:OnNewModule(module) end function signaler:RegisterModule(modules) for _, module in pairs(modules) do self:OnNewModule(module); module:OnNewModule(self); end modules[#modules + 1] = self; self._ids_start = (#modules) * 1000; end function signaler:ToJSON(item) local json = {}; function json:AddStr(name, value) local separator = ""; if self.str ~= nil then separator = ","; else self.str = ""; end self.str = self.str .. string.format("%s\"%s\":\"%s\"", separator, tostring(name), tostring(value)); end function json:AddNumber(name, value) local separator = ""; if self.str ~= nil then separator = ","; else self.str = ""; end self.str = self.str .. string.format("%s\"%s\":%f", separator, tostring(name), value or 0); end function json:AddBool(name, value) local separator = ""; if self.str ~= nil then separator = ","; else self.str = ""; end self.str = self.str .. string.format("%s\"%s\":%s", separator, tostring(name), value and "true" or "false"); end function json:ToString() return "{" .. (self.str or "") .. "}"; end local first = true; for idx,t in pairs(item) do local stype = type(t) if stype == "number" then json:AddNumber(idx, t); elseif stype == "string" then json:AddStr(idx, t); elseif stype == "boolean" then json:AddBool(idx, t); elseif stype == "function" or stype == "table" then --do nothing else core.host:trace(tostring(idx) .. " " .. tostring(stype)); end end return json:ToString(); end function signaler:ArrayToJSON(arr) local str = "["; for i, t in ipairs(self._alerts) do local json = self:ToJSON(t); if str == "[" then str = str .. json; else str = str .. "," .. json; end end return str .. "]"; end function signaler:AsyncOperationFinished(cookie, success, message, message1, message2) if cookie == self._telegram_timer and #self._alerts > 0 and (self.last_req == nil or not self.last_req:loading()) then if self._external_service_key == nil then return; end local data = self:ArrayToJSON(self._alerts); self._alerts = {}; self.last_req = http_lua.createRequest(); local query = string.format('{"Key":"%s","StrategyName":"%s","Platform":"FXTS2","Notifications":%s}', self._external_service_key, string.gsub(self.StrategyName or "", '"', '\\"'), data); self.last_req:setRequestHeader("Content-Type", "application/json"); self.last_req:setRequestHeader("Content-Length", tostring(string.len(query))); self.last_req:start("http://profitrobots.com/api/v1/notification", "POST", query); end end function signaler:FormatEmail(source, period, message) --format email subject local subject = message .. "(" .. source:instrument() .. ")"; --format email text local delim = "\013\010"; local signalDescr = "Signal: " .. (self.StrategyName or ""); local symbolDescr = "Symbol: " .. source:instrument(); local messageDescr = "Message: " .. message; local ttime = core.dateToTable(core.host:execute("convertTime", 1, 4, source:date(period))); local dateDescr = string.format("Time: %02i/%02i %02i:%02i", ttime.month, ttime.day, ttime.hour, ttime.min); local priceDescr = "Price: " .. source[period]; local text = "You have received this message because the following signal alert was received:" .. delim .. signalDescr .. delim .. symbolDescr .. delim .. messageDescr .. delim .. dateDescr .. delim .. priceDescr; return subject, text; end function signaler:Signal(label, source) if source == nil then source = instance.bid; if instance.bid == nil then local pane = core.host.Window.CurrentPane; source = pane.Data:getStream(0); else source = instance.bid; end end if self._show_alert then terminal:alertMessage(source:instrument(), source[NOW], label, source:date(NOW)); end if self._sound_file ~= nil then terminal:alertSound(self._sound_file, self._recurrent_sound); end if self._email ~= nil then terminal:alertEmail(self._email, profile:id().. " : " .. label, self:FormatEmail(source, NOW, label)); end if self._external_service_key ~= nil then self:AlertTelegram(label, source:instrument(), source:barSize()); end end function signaler:AlertTelegram(message, instrument, timeframe) if core.host.Trading:getTradingProperty("isSimulation") then return; end local alert = {}; alert.Text = message or ""; alert.Instrument = instrument or ""; alert.TimeFrame = timeframe or ""; self._alerts[#self._alerts + 1] = alert; end function signaler:Init(parameters) parameters:addBoolean("signaler_show_alert", "Show Alert", "", true); parameters:addBoolean("signaler_play_sound", "Play Sound", "", false); parameters:addFile("signaler_sound_file", "Sound File", "", ""); parameters:setFlag("signaler_sound_file", core.FLAG_SOUND); parameters:addBoolean("signaler_recurrent_sound", "Recurrent Sound", "", true); parameters:addBoolean("signaler_send_email", "Send Email", "", false); parameters:addString("signaler_email", "Email", "", ""); parameters:setFlag("signaler_email", core.FLAG_EMAIL); parameters:addBoolean("use_external_service", "Send to external service", "Telegram message or Channel post", false); parameters:addString("external_service_key", "External service Key", "You can get it via @profit_robots_bot Telegram bot", ""); end function signaler:Prepare(name_only) if instance.parameters.signaler_play_sound then self._sound_file = instance.parameters.signaler_sound_file; assert(self._sound_file ~= "", "Sound file must be chosen"); end self._show_alert = instance.parameters.signaler_show_alert; self._recurrent_sound = instance.parameters.signaler_recurrent_sound; if instance.parameters.signaler_send_email then self._email = instance.parameters.signaler_email; assert(self._email ~= "", "E-mail address must be specified"); end --do what you usually do in prepare if name_only then return; end if instance.parameters.external_service_key ~= "" and instance.parameters.use_external_service then self._external_service_key = instance.parameters.external_service_key; require("http_lua"); self._telegram_timer = self._ids_start + 1; core.host:execute("setTimer", self._telegram_timer, 1); end end signaler:RegisterModule(Modules);