--+------------------------------------------------------------------+ --| Copyright © 2020, Gehtsoft USA LLC | --| http://fxcodebase.com | --+------------------------------------------------------------------+ --| Developed by : Mario Jemic | --| mario.jemic@gmail.com | --| https://AppliedMachineLearning.systems | --+------------------------------------------------------------------+ --| Support our efforts by donating | --| Paypal: https://goo.gl/9Rj74e | --| Patreon: https://goo.gl/GdXWeN | --+------------------------------------------------------------------+ --=============================================================================-- -- INDICATOR SPECIFIC FUNCTIONS TO IMPLEMENT -- --=============================================================================-- function getName() return "Multitimeframe Elder Impulse System Heat Map" end function isTick() return true end function addIndicatorParameters() addIndicatorParam(1, "H1", 13, 12, 26); addIndicatorParam(2, "H2", 13, 12, 26); addIndicatorParam(3, "H4", 13, 12, 26); addIndicatorParam(4, "H6", 13, 12, 26); addIndicatorParam(5, "H8", 13, 12, 26); end -- Add a group of time-frame related parameters function addIndicatorParam(id, frame, PARAM1, PARAM2, PARAM3) indicator.parameters:addBoolean("use_" .. id, "Use timeframe #" .. id, "", true); indicator.parameters:addString("B" .. id, "Time frame for average " .. id, "", frame); indicator.parameters:setFlag("B" .. id, core.FLAG_PERIODS); indicator.parameters:addInteger("EMA" .. id, "EMA " .. id .. "EMA ", "", PARAM1); indicator.parameters:addInteger("MACDF" .. id, "MACDF " .. id .. "MACDF ", "", PARAM2); indicator.parameters:addInteger("MACDS" .. id, "MACDS " .. id .. "MACDS ", "", PARAM3); end function addColorParameters() indicator.parameters:addColor("clrUP", "Up color", "", core.rgb(0, 255, 0)); indicator.parameters:addColor("clrDN", "Down color", "", core.rgb(255, 0, 0)); end function getLabel(id) return instance.parameters:getString("B" .. id) .. "(" .. instance.parameters:getString("EMA" .. id).. " " .. instance.parameters:getString("MACDF" .. id).. " " .. instance.parameters:getString("MACDS" .. id) .. ")" end function createIndicator(id, source) local profile = core.indicators:findIndicator("ELDER_IMPULSE_SYSTEM"); assert(profile ~= nil, "Please, download and install " .. "ELDER_IMPULSE_SYSTEM" .. ".LUA indicator"); local indicatorParams = profile:parameters(); return core.indicators:create("ELDER_IMPULSE_SYSTEM", source, instance.parameters:getInteger("EMA" .. id), instance.parameters:getInteger("MACDF" .. id), instance.parameters:getInteger("MACDS" .. id), instance.parameters.clrUP, instance.parameters.clrDN, instance.parameters.defaultColor); end function getColor(indicator, period) return indicator.DATA:colorI(period) end --=============================================================================-- -- INDICATOR SPECIFIC FUNCTIONS TO IMPLEMENT -- --=============================================================================-- -- Sources v1.3 local sources = {} sources.last_id = 1 sources.ids = {} sources.items = {} function sources:Request(id, source, tf, isBid, instrument) local ids = {} ids.loading_id = self.last_id ids.loaded_id = self.last_id + 1 ids.loaded = false self.last_id = self.last_id + 2 self.ids[id] = ids if tf == nil then tf = source:barSize() end if isBid == nil then isBid = source:isBid() end if instrument == nil then instrument = source:instrument(); end self.items[id] = core.host:execute("getSyncHistory", instrument, tf, isBid, 100, ids.loaded_id, ids.loading_id) return self.items[id]; end function sources:AsyncOperationFinished(cookie, successful, message, message1, message2) for index, ids in pairs(self.ids) do if ids.loaded_id == cookie then ids.loaded = true self.allLoaded = nil return true elseif ids.loading_id == cookie then ids.loaded = false self.allLoaded = false return false end end return false end function sources:IsAllLoaded() if self.allLoaded == nil then for index, ids in pairs(self.ids) do if not ids.loaded then self.allLoaded = false return false end end self.allLoaded = true end return self.allLoaded end -- -- initialization function -- function Init() indicator:name("Multitimeframe " .. getName() .. " Heat Map"); indicator:description(""); indicator:requiredSource(core.Bar); indicator:type(core.Oscillator); indicator.parameters:addGroup("Calculation"); --========================================================================= addIndicatorParameters() --========================================================================= if isTick() then indicator.parameters:addString("Price", "Price", "", "close"); indicator.parameters:addStringAlternative("Price", "open", "", "open"); indicator.parameters:addStringAlternative("Price", "close", "", "close"); indicator.parameters:addStringAlternative("Price", "high", "", "high"); indicator.parameters:addStringAlternative("Price", "low", "", "low"); indicator.parameters:addStringAlternative("Price", "typical", "", "typical"); indicator.parameters:addStringAlternative("Price", "median", "", "median"); indicator.parameters:addStringAlternative("Price", "weighted", "", "weighted"); end indicator.parameters:addGroup("Display"); indicator.parameters:addString("VT", "Visualization", "", "bar"); indicator.parameters:addStringAlternative("VT", "Line", "", "line"); indicator.parameters:addStringAlternative("VT", "Bar", "", "bar"); --========================================================================= addColorParameters() --========================================================================= indicator.parameters:addColor("defaultColor", "Neutral color", "", core.rgb(0, 0, 255)); indicator.parameters:addColor("labelColor", "Label color", "", core.COLOR_LABEL); end -- list of streams local streams = {} -- list of attached streams local source; -- source prices local day_offset; -- offset of the trading day against calendar midnight local week_offset; -- offset of the trading week against Sunday local dummy; -- dummy stream local host; -- a reference to host (perf. issue) local PriceType; -- price type used local source_first; -- first bar of the source local second = 1.0 / 86400.0; -- one second length -- prepare the indicator for execution function Prepare(onlyName) -- cache the data source = instance.source; source_first = source:first(); host = core.host; if isTick() then PriceType = instance.parameters.Price; end day_offset = host:execute("getTradingDayOffset"); week_offset = host:execute("getTradingWeekOffset"); -- validate parameters checkBarSize(1); checkBarSize(2); checkBarSize(3); checkBarSize(4); checkBarSize(5); -- make the indicator label local i; local name = profile:id() .. "(" .. source:name() .. ","; if isTick() then name = name .. PriceType .. ","; end for i = 1, 5, 1 do name = name .. getLabel(i); end name = name .. ")"; instance:name(name); if onlyName then return ; end -- colorize the indicator label and set range dummy = instance:addStream("D", core.Line, name .. ".D", "D", instance.parameters.labelColor, 0); dummy:addLevel(0); dummy:addLevel(120); -- create output stream for timeframe data for i = 1, 5, 1 do if instance.parameters:getBoolean("use_" .. i) then streams[#streams + 1] = registerStream(i, instance.parameters:getString("B" .. i), 300, getLabel(i)); end end end -- update the indicator values function Update(period, mode) if not sources:IsAllLoaded() then return; end if period == source:size() - 1 then for i, stream in ipairs(streams) do updateOutput(stream, (6 - i) * 20, mode); -- if the current value has been changed - update the whole candle backward if source:isAlive() and stream.external and period > source_first and math.abs(stream.ref_candle[period] - stream.ref_candle[period - 1]) < second and stream.output:colorI(period) ~= stream.output:colorI(period - 1) then local t = period - 1; local output = stream.output; local color = stream.output:colorI(period); local v = (6 - i) * 20; while t > output:first() and math.abs(stream.ref_candle[period] - stream.ref_candle[t]) < second do output[t] = v; output:setColor(t, color); t = t - 1; end end end end end -- the function is called when the async operation is finished function AsyncOperationFinished(cookie, successful, message, message1, message2) if sources:AsyncOperationFinished(cookie, successful, message, message1, message2) then instance:updateFrom(0); end end -- validate the size of the chosen time frame function checkBarSize(id) local s, e, s1, e1; s, e = core.getcandle(source:barSize(), core.now(), 0, 0); s1, e1 = core.getcandle(instance.parameters:getString("B" .. id), core.now(), 0, 0); assert ((e - s) <= (e1 - s1), "The chosen time frame must be equal to or bigger than the chart time frame!"); end -- register a stream for further processing -- @param id The identifier of the stream -- @param barSize Stream's bar size -- @param extent The size of the required exten -- @param label The label of the stream function registerStream(id, barSize, extent, label) local stream = {}; local s1, e1, length; local from, to; s1, e1 = core.getcandle(barSize, core.now(), 0, 0); length = math.floor((e1 - s1) * 86400 + 0.5); stream.barSize = barSize; -- name of the time frame stream.label = label; -- label stream.length = length; -- length of the bar in seconds stream.extent = extent; -- extent of the data in bars if barSize == source:barSize() then -- if the size of the timeframe requested is equal to the size of the source -- use the indicator source stream.external = false; stream.loading = false; stream.data = source; stream.fullUpdate = true; else -- else prepare everything for further update data loading stream.data = nil; stream.external = true; stream.loading = false; stream.fullUpdate = false; end -- create an output if instance.parameters.VT == "line" then stream.output = instance:addStream("O" .. id, core.Line, "O" .. id, "O" .. id, instance.parameters.defaultColor, 0); stream.output:setWidth(5); else stream.output = instance:addStream("H" .. id, core.Line, "H" .. id, "H" .. id, instance.parameters.defaultColor, 0); stream.open = instance:addStream("O" .. id, core.Line, "O" .. id, "O" .. id, instance.parameters.defaultColor, 0); stream.low = instance:addStream("L" .. id, core.Line, "L" .. id, "L" .. id, instance.parameters.defaultColor, 0); stream.close = instance:addStream("C" .. id, core.Line, "C" .. id, "C" .. id, instance.parameters.defaultColor, 0); instance:createCandleGroup ("O" .. id, id, stream.open, stream.output, stream.low, stream.close) end -- if stream is external prepare the cache for data if stream.external then stream.ref_candle = instance:addInternalStream(0, 0); end -- the place for the indicator stream.data = sources:Request(id, source, stream.barSize); indi_src = stream.data if isTick() then indi_src = indi_src[instance.parameters.Price] end stream.indicator = createIndicator(id, indi_src); return stream; end -- updates the indicator output according the stream -- stream - the stream to be updated function updateOutput(stream, level, mode) local i, from, to, candle_from, candle_to, indi_color, p; stream.indicator:update(mode); if stream.fullUpdate then from = 0; else from = stream.output:size() - 1; end to = stream.output:size() - 1; if stream.external then candle_to = 0; indi_color = nil; for i = from, to, 1 do local date = source:date(i); if date >= candle_to then candle_from, candle_to = core.getcandle(stream.barSize, date, day_offset, week_offset); local p; p = core.findDate(stream.data, candle_from, true); if p < 0 then indi_color = nil; else indi_color = getColor(stream.indicator, p); end end stream.ref_candle[i] = candle_from; if indi_color ~= nil then if instance.parameters.VT == "line" then stream.output[i] = level; stream.output:setColor(i, indi_color); else stream.output[i] = level - 10 + 1; stream.open[i] = level - 10 + 1; stream.low[i] = level + 10 - 1; stream.close[i] = level + 10 - 1; stream.output:setColor(i, indi_color); stream.open:setColor(i, indi_color); stream.low:setColor(i, indi_color); stream.close:setColor(i, indi_color); end else stream.output[i] = nil --:setNoData(i); end end else for i = from, to, 1 do if stream.indicator.DATA:hasData(i) then indi_color = getColor(stream.indicator, i) if instance.parameters.VT == "line" then stream.output[i] = level; stream.output:setColor(i, indi_color); else stream.output[i] = level - 10 + 1; stream.open[i] = level - 10 + 1; stream.low[i] = level + 10 - 1; stream.close[i] = level + 10 - 1; stream.output:setColor(i, indi_color); stream.open:setColor(i, indi_color); stream.low:setColor(i, indi_color); stream.close:setColor(i, indi_color); end else stream.output[i] = nil; -- :setNoData(i); end end end stream.updateFull = false; end