-- More information about this indicator can be found at: -- http://fxcodebase.com/code/viewtopic.php?f=17&t=68490 --+------------------------------------------------------------------+ --| Copyright © 2019, Gehtsoft USA LLC | --| http://fxcodebase.com | --+------------------------------------------------------------------+ --| Developed by : Mario Jemic | --| mario.jemic@gmail.com | --+------------------------------------------------------------------+ --| Support our efforts by donating | --| Paypal: https://goo.gl/9Rj74e | --+------------------------------------------------------------------+ --| Patreon : https://goo.gl/GdXWeN | --| BitCoin : 15VCJTLaz12Amr7adHSBtL9v8XomURo9RF | --| BitCoin Cash: 1BEtS465S3Su438Kc58h2sqvVvHK9Mijtg | --| Ethereum : 0x8C110cD61538fb6d7A2B47858F0c0AaBd663068D | --| LiteCoin : LLU8PSY2vsq7B9kRELLZQcKf5nJQrdeqwD | --+------------------------------------------------------------------+ -- Dashboard template v.1.6 local timeframes_list = {"m1", "m5", "m15", "m30", "H1", "H2", "H3", "H4", "H6", "H8", "D1", "W1", "M1"}; local timeframes_modes = {"disabled", "disabled", "disabled", "disabled", "display", "disabled", "disabled", "disabled", "disabled", "disabled", "display", "display", "display"}; local Modules = {}; -- USER DEFINITIONS SECTION local indi_name = "FOREX DASHBOARD EXCEED BB"; local indi_id = "BB"; local indi_version = "1"; function CreateIndicators(source) local indicators = {}; local indi = core.indicators:findIndicator(indi_id); assert(indi ~= nil, "Please download and install " .. indi_id .. ".lua indicator"); local p = indi:parameters(); p:setInteger("Period", instance.parameters.Period); p:setDouble("Deviation", instance.parameters.Deviation); indicators[#indicators + 1] = indi:createInstance(source, p); return indicators; end function CreateParameters() indicator.parameters:addInteger("Period", "Period", "Period", 20); indicator.parameters:addDouble("Deviation", "Number of Standard Deviation", "Number of Standard Deviation", 2); end function GetLastSignal(indi, source) if core.crossesOver(source.close, indi[1].TL, indi[1].TL:size() - 1) then return 1, source:date(NOW), "B"; end if core.crossesUnder(source.close, indi[1].BL, indi[1].BL:size() - 1) then return -1, source:date(NOW), "S"; end return 0, nil, "-"; end -- ENF OF USER DEFINITIONS SECTION function Init() indicator:name(indi_name .. " v." .. indi_version); indicator:description(""); indicator:requiredSource(core.Bar); indicator:type(core.Oscillator); indicator:setTag("Version", indi_version) CreateParameters(); indicator.parameters:addGroup("Instruments"); indicator.parameters:addBoolean("all_instruments", "All instruments", "", false); for i = 1, 20, 1 do Add(i); end indicator.parameters:addGroup("Time Frame Selector"); for i = 1, #timeframes_list do AddTimeFrame(i, timeframes_list[i], timeframes_modes[i]); end indicator.parameters:addGroup("DDE"); indicator.parameters:addBoolean("dde_export_values", "Export DDE", "", false); indicator.parameters:addString("dde_service", "Service Name", "The service name must be unique amoung all running instances of the strategy", "DASHBOARD"); indicator.parameters:addString("dde_topic", "DDE Topic", "value = instrument_timeframe. Ex: EURUSD_m1", "Values"); indicator.parameters:addGroup("Styling"); indicator.parameters:addColor("up_color", "Up color", "", core.rgb(0, 255, 0)); indicator.parameters:addColor("dn_color", "Down color", "", core.rgb(255, 0, 0)); indicator.parameters:addColor("text_color", "Text color", "", core.rgb(0, 0, 0)); indicator.parameters:addColor("background_color", "Background color", "", core.rgb(255, 255, 255)); indicator.parameters:addColor("signal_background_color", "Active signal background color", "", core.rgb(250, 250, 210)); indicator.parameters:addInteger("load_quota", "Loading quota", "Prevents freeze. Use 0 to disable", 0); indicator.parameters:addDouble("cells_gap", "Gap coefficient", "", 1.2); indicator.parameters:addColor("grid_color", "Grid color", "", core.rgb(128, 128, 128)); indicator.parameters:addBoolean("draw_grid", "Draw grid", "", false); indicator.parameters:addString("grid_mode", "Grid mode", "", "v") indicator.parameters:addStringAlternative("grid_mode", "Horizontal", "", "h") indicator.parameters:addStringAlternative("grid_mode", "Vertical", "", "v") indicator.parameters:addGroup("Alerts"); signaler:Init(indicator.parameters); end function Add(id) local Init = {"EUR/USD", "USD/JPY", "GBP/USD", "USD/CHF", "EUR/CHF" , "AUD/USD", "USD/CAD", "NZD/USD", "EUR/GBP", "EUR/JPY" , "GBP/JPY", "CHF/JPY", "GBP/CHF", "EUR/AUD", "EUR/CAD" , "AUD/CAD", "AUD/JPY", "CAD/JPY", "NZD/JPY", "GBP/CAD" }; indicator.parameters:addBoolean("use_pair" .. id, "Use This Slot", "", id <= 5); indicator.parameters:addString("Pair" .. id, "Pair", "", Init[id]); indicator.parameters:setFlag("Pair" .. id, core.FLAG_INSTRUMENTS); end function AddTimeFrame(id, FRAME, DEFAULT) local paramId = "Use" .. id; indicator.parameters:addString(paramId, FRAME, "", DEFAULT); indicator.parameters:addStringAlternative(paramId, "Do not use", "", "disabled"); indicator.parameters:addStringAlternative(paramId, "Display + Alert", "", "both"); indicator.parameters:addStringAlternative(paramId, "Display only", "", "display"); indicator.parameters:addStringAlternative(paramId, "Alert only", "", "alert"); end local items = {}; local instruments = {}; local timeframes = {}; local text_color; local TIMER_ID = 1; local last_id = 1; local dde_server, dde_topic; function PrepareInstrument(instrument) local timeframe_index = 1; for ii = 1, #timeframes_list do use = instance.parameters:getString("Use" .. ii); if use ~= "no" then local symbol = {}; symbol.Mode = use; symbol.Pair = instrument; symbol.Point = core.host:findTable("offers"):find("Instrument", symbol.Pair).PointSize; symbol.TF = timeframes_list[ii]; symbol.LoadingId = last_id + 1; symbol.LoadedId = last_id + 2; symbol.Loading = true; symbol.SymbolIndex = #instruments + 1; if use == "both" or use == "display" then symbol.TimeframeIndex = timeframe_index; end function symbol:DoLoad() self.Source = core.host:execute("getSyncHistory", self.Pair, self.TF, instance.source:isBid(), 300, self.LoadedId, self.LoadingId); self.Indicators = CreateIndicators(self.Source); end if instance.parameters.dde_export_values then symbol.dde = dde_server:addValue(dde_topic, string.gsub(instrument, "/", "") .. "_" .. symbol.TF); end last_id = last_id + 2; items[#items + 1] = symbol; if use == "both" or use == "display" then timeframes[timeframe_index] = timeframes_list[ii]; timeframe_index = timeframe_index + 1; end end end instruments[#instruments + 1] = instrument; end local timer_handle; -- Cells builder v.1.3 local CellsBuilder = {}; CellsBuilder.GapCoeff = 1.2; function CellsBuilder:Clear(context) self.Columns = {}; self.RowHeights = {}; self.Context = context; end function CellsBuilder:Add(font, text, color, column, row, mode, backgound, grid_pen, grid_top, grid_bottom) if self.Columns[column] == nil then self.Columns[column] = {}; self.Columns[column].Rows = {}; self.Columns[column].MaxWidth = 0; self.Columns[column].MaxHeight = 0; self.Columns[column].MaxRowIndex = 0; end local cell = {}; cell.Text = text; cell.Font = font; cell.Color = color; local w, h = self.Context:measureText(font, text, mode); cell.Width = w; cell.Height = h; cell.Mode = mode; cell.Background = backgound; cell.GridPen = grid_pen; cell.DrawGridTop = grid_top; cell.DrawGridBottom = grid_bottom; self.Columns[column].Rows[row] = cell; if self.Columns[column].MaxRowIndex < row then self.Columns[column].MaxRowIndex = row; end if self.Columns[column].MaxWidth < w then self.Columns[column].MaxWidth = w; end if self.RowHeights[row] == nil or self.RowHeights[row] < h then self.RowHeights[row] = h; end end function CellsBuilder:GetTotalWidth() local width = 0; for columnIndex, column in ipairs(self.Columns) do width = width + column.MaxWidth * self.GapCoeff; end return width; end function CellsBuilder:GetTotalHeight() local height = 0; for i = 0, self.Columns[1].MaxRowIndex do if self.RowHeights[i] ~= nil then height = height + self.RowHeights[i] * self.GapCoeff; end end return height; end function CellsBuilder:Draw(x, y) local max_height = self:GetTotalHeight(); local max_width = self:GetTotalWidth(); local total_width = 0; for columnIndex, column in ipairs(self.Columns) do local total_height = 0; for i = 0, column.MaxRowIndex do local cell = column.Rows[i]; if cell ~= nil then local background = -1; if cell.Background ~= nil then background = cell.Background; end local x_start = x + total_width; local y_start = y + total_height; local x_end = x + total_width + column.MaxWidth * self.GapCoeff; local y_end = y + total_height + self.RowHeights[i] * self.GapCoeff; self.Context:drawText(cell.Font, cell.Text, cell.Color, background, x_start + column.MaxWidth * (self.GapCoeff - 1) / 2, y_start + self.RowHeights[i] * (self.GapCoeff - 1) / 2, x_end, y_end, cell.Mode); if cell.GridPen ~= nil then if cell.DrawGridTop then self.Context:drawLine(cell.GridPen, x_start, y_start, x_end, y_start); -- top end if cell.DrawGridBottom then self.Context:drawLine(cell.GridPen, x_start, y_end, x_end, y_end); -- bottom end self.Context:drawLine(cell.GridPen, x_start, y_start, x_start, y_end); -- left self.Context:drawLine(cell.GridPen, x_end, y_start, x_end, y_end); -- right end end if self.RowHeights[i] ~= nil then total_height = total_height + self.RowHeights[i] * self.GapCoeff; end end total_width = total_width + column.MaxWidth * self.GapCoeff; end end function Prepare(nameOnly) signaler:Prepare(nameOnly); instance:name(indi_name); if nameOnly then return; end text_color = instance.parameters.text_color; if instance.parameters.dde_export_values then if ddeserver_lua == nil then require("ddeserver_lua"); end dde_server = ddeserver_lua.new(instance.parameters.dde_service); dde_topic = dde_server:addTopic(instance.parameters.dde_topic); end if instance.parameters.all_instruments then local enum = core.host:findTable("offers"):enumerator(); local row = enum:next(); while (row ~= nil) do PrepareInstrument(row.Instrument); row = enum:next(); end else for i = 1, 20, 1 do if instance.parameters:getBoolean("use_pair" .. i) then PrepareInstrument(instance.parameters:getString("Pair" .. i)); end end end timer_handle = core.host:execute("setTimer", TIMER_ID, 1); core.host:execute("setStatus", "Loading"); instance:ownerDrawn(true); CellsBuilder.GapCoeff = instance.parameters.cells_gap; end function FormatTime(time) local diff = core.host:execute("getServerTime") - time; if (diff > 1) then return math.floor(diff) .. " d."; end local diff_date = core.dateToTable(diff); if (diff_date.hour > 0) then return diff_date.hour .. " h."; end if (diff_date.min > 0) then return diff_date.min .. " min."; end return "now"; end local init = false; local FONT = 1; local FONT_TEXT = 2; local BG_PEN = 3; local BG_BRUSH = 4; local GRID_PEN = 5; local draw_grid, grid_mode; function GetTableIndex(symbol) if grid_mode == "h" then return (symbol.TimeframeIndex + 1) * 2, symbol.SymbolIndex + 1; end return (symbol.SymbolIndex + 1) * 2, symbol.TimeframeIndex + 1; end function DrawSignal(symbol, context) if (symbol.Mode ~= "both" and symbol.Mode ~= "display") or symbol.Text == nil then return; end local row, column = GetTableIndex(symbol); if symbol.Signal == 0 or symbol.Signal == nil then CellsBuilder:Add(FONT_TEXT, symbol.Text, text_color, column, row, context.CENTER, backgound, GRID_PEN, true, false); CellsBuilder:Add(FONT_TEXT, symbol.Text, text_color, column, row + 1, context.CENTER, backgound, GRID_PEN, false, true); return; end local backgound = -1; if symbol.Source:date(NOW) <= symbol.SignalTime then backgound = instance.parameters.signal_background_color; end local color = symbol.Signal > 0 and instance.parameters.up_color or instance.parameters.dn_color; CellsBuilder:Add(FONT_TEXT, symbol.Text, color, column, row, context.CENTER, backgound, GRID_PEN, true, false); CellsBuilder:Add(FONT_TEXT, FormatTime(symbol.SignalTime), text_color, column, row + 1, context.CENTER, backgound, GRID_PEN, false, true); end function Draw(stage, context) if stage ~= 2 then return; end if not init then context:createFont(FONT, "Wingdings", 0, context:pointsToPixels(8), 0) context:createFont(FONT_TEXT, "Arial", 0, context:pointsToPixels(8), 0) context:createPen(BG_PEN, context.SOLID, 1, instance.parameters.background_color); context:createSolidBrush(BG_BRUSH, instance.parameters.background_color); draw_grid = instance.parameters.draw_grid; grid_mode = instance.parameters.grid_mode; if draw_grid then context:createPen(GRID_PEN, context.SOLID, 1, instance.parameters.grid_color); else GRID_PEN = nil; end init = true; end local title_w, title_h = context:measureText(FONT_TEXT, indi_name, 0); CellsBuilder:Clear(context); for i = 1, #timeframes do if grid_mode == "h" then CellsBuilder:Add(FONT_TEXT, timeframes[i], text_color, 1, (i + 1) * 2, context.LEFT); else CellsBuilder:Add(FONT_TEXT, timeframes[i], text_color, i + 1, 1, context.LEFT); end end for i = 1, #instruments do if grid_mode == "h" then CellsBuilder:Add(FONT_TEXT, instruments[i], text_color, i + 1, 1, context.CENTER); else CellsBuilder:Add(FONT_TEXT, instruments[i], text_color, 1, (i + 1) * 2, context.CENTER); end end for _, symbol in ipairs(items) do DrawSignal(symbol, context); end local width = math.max(title_w, CellsBuilder:GetTotalWidth()); context:drawRectangle(BG_PEN, BG_BRUSH, context:right() - width, context:top(), context:right(), context:top() + title_h * 1.2 + CellsBuilder:GetTotalHeight()); context:drawText(FONT_TEXT, indi_name, text_color, -1, context:right() - width, context:top(), context:right(), context:top() + title_h, 0); CellsBuilder:Draw(context:right() - width, context:top() + title_h * 1.2); end function Update(period, mode) for _, module in pairs(Modules) do if module.ExtUpdate ~= nil then module:ExtUpdate(nil, nil, nil); end end end function UpdateData() for _, symbol in ipairs(items) do if symbol.Indicators ~= nil and not symbol.Loading then for i, indicator in ipairs(symbol.Indicators) do indicator:update(core.UpdateLast); end local signal, time, label = GetLastSignal(symbol.Indicators, symbol.Source); symbol.Signal = signal; symbol.SignalTime = time; symbol.Text = label; if signal ~= 0 then local is_current_bar = symbol.Source:date(NOW) <= time; if is_current_bar and symbol.last_alert ~= time and (symbol.Mode == "both" or symbol.Mode == "alert") then signaler:Signal(symbol.Source:instrument() .. "(" .. symbol.Source:barSize() .. "): " .. symbol.Text); symbol.last_alert = time; end end if symbol.dde ~= nil then dde_server:set(dde_topic, symbol.dde, symbol.Text); end else symbol.Text = "..."; symbol.Signal = 0; end end end local loading_finished = false; function AsyncOperationFinished(cookie, success, message, message1, message2) for _, module in pairs(Modules) do if module.AsyncOperationFinished ~= nil then module:AsyncOperationFinished(cookie, success, message, message1, message2); end end if cookie == TIMER_ID then UpdateData(); if not loading_finished then local loading_count = 0; for _, symbol in ipairs(items) do if symbol.Source == nil then symbol:DoLoad(); loading_count = loading_count + 1; elseif symbol.Loading then loading_count = loading_count + 1; end if loading_count == instance.parameters.load_quota and instance.parameters.load_quota > 0 then return; end end core.host:execute("setStatus", ""); loading_finished = true; end else for _, symbol in ipairs(items) do if cookie == symbol.LoadingId then symbol.Loading = true; return; elseif cookie == symbol.LoadedId then symbol.Loading = false; return; end end end end signaler = {}; signaler.Name = "Signaler"; signaler.Debug = false; signaler.Version = "1.5"; signaler._show_alert = nil; signaler._sound_file = nil; signaler._recurrent_sound = nil; signaler._email = nil; signaler._ids_start = nil; signaler._advanced_alert_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) * 100; 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._advanced_alert_timer and #self._alerts > 0 and (self.last_req == nil or not self.last_req:loading()) then if self._advanced_alert_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._advanced_alert_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", core.TZ_EST, self._ToTime, 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(message, source) if source == nil then if instance.source ~= nil then source = instance.source; elseif instance.bid ~= nil then source = instance.bid; else local pane = core.host.Window.CurrentPane; source = pane.Data:getStream(0); end end if self._show_alert then terminal:alertMessage(source:instrument(), source[NOW], message, 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().. " : " .. message, self:FormatEmail(source, NOW, message)); end if self._advanced_alert_key ~= nil then self:AlertTelegram(message, source:instrument(), source:barSize()); end if self._signaler_debug_alert then core.host:trace(message); end if self._show_popup then local subject, text = self:FormatEmail(source, NOW, message); core.host:execute("prompt", self._ids_start + 2, subject, text); end if self._dde_alerts then dde_server:set(self.dde_topic, self.dde_alerts, message); 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:addInteger("signaler_ToTime", "Convert the date to", "", 6) parameters:addIntegerAlternative("signaler_ToTime", "EST", "", 1) parameters:addIntegerAlternative("signaler_ToTime", "UTC", "", 2) parameters:addIntegerAlternative("signaler_ToTime", "Local", "", 3) parameters:addIntegerAlternative("signaler_ToTime", "Server", "", 4) parameters:addIntegerAlternative("signaler_ToTime", "Financial", "", 5) parameters:addIntegerAlternative("signaler_ToTime", "Display", "", 6) 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); if indicator ~= nil and strategy == nil then parameters:addBoolean("signaler_show_popup", "Show Popup", "", false); end parameters:addBoolean("signaler_debug_alert", "Print Into Log", "", false); parameters:addBoolean("use_advanced_alert", "Send Advanced Alert", "Telegram/Discord/other platform (like MT4)", false) parameters:addString("advanced_alert_key", "Advanced Alert Key", "You can get a key via @profit_robots_bot Telegram Bot. Visit ProfitRobots.com for discord/other platform keys", "") if DDEAlertsSupport then parameters:addBoolean("signaler_dde_export", "DDE Export", "You can export the alert into the Excel or any other application with DDE support (=Service Name|DDE Topic!Alerts)", false); parameters:addString("signaler_dde_service", "Service Name", "The service name must be unique amoung all running instances of the strategy", "TS2ALERTS"); parameters:addString("signaler_dde_topic", "DDE Topic", "", ""); end end function signaler:Prepare(name_only) self._ToTime = instance.parameters.signaler_ToTime if self._ToTime == 1 then self._ToTime = core.TZ_EST elseif self._ToTime == 2 then self._ToTime = core.TZ_UTC elseif self._ToTime == 3 then self._ToTime = core.TZ_LOCAL elseif self._ToTime == 4 then self._ToTime = core.TZ_SERVER elseif self._ToTime == 5 then self._ToTime = core.TZ_FINANCIAL elseif self._ToTime == 6 then self._ToTime = core.TZ_TS end self._dde_alerts = instance.parameters.signaler_dde_export; if self._dde_alerts then assert(instance.parameters.signaler_dde_topic ~= "", "You need to specify the DDE topic"); require("ddeserver_lua"); self.dde_server = ddeserver_lua.new(instance.parameters.signaler_dde_service); self.dde_topic = self.dde_server:addTopic(instance.parameters.signaler_dde_topic); self.dde_alerts = self.dde_server:addValue(self.dde_topic, "Alerts"); end 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; self._show_popup = instance.parameters.signaler_show_popup; self._signaler_debug_alert = instance.parameters.signaler_debug_alert; 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.advanced_alert_key ~= "" and instance.parameters.use_advanced_alert then self._advanced_alert_key = instance.parameters.advanced_alert_key; require("http_lua"); self._advanced_alert_timer = self._ids_start + 1; core.host:execute("setTimer", self._advanced_alert_timer, 1); end end function signaler:ReleaseInstance() if self.dde_server ~= nil then self.dde_server:close(); end end signaler:RegisterModule(Modules);