--=============================================================================-- -- 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: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) 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 -- --=============================================================================-- -- -- 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 stream = registerStream(i, instance.parameters:getString("B" .. i), 300, getLabel(i)); end end -- update the indicator values function Update(period, mode) -- first call or further pre-loading of the data if period == source_first then for i = 1, 5, 1 do updateStream(i); end end if period == source:size() - 1 then for i = 1, 5, 1 do local stream = streams[i]; local label = stream.label; if streams[i].loading then label = label .. "," .. "(loading)"; end host:execute("drawLabel", i, source:date(period), (6 - i) * 20, label); if stream.data:size() > 0 then updateOutput(stream, (6 - i) * 20, mode); end if not loading then -- 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 end -- the function is called when the async operation is finished function AsyncOperationFinished(cookie) registerDataLoaded(cookie); 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.indicator = nil; streams[id] = stream; end -- The function checks whether the range of the source stream and additional streams are synchronized function updateStream(id) local stream = streams[id]; assert(stream ~= nil, "Stream is not registered"); if stream.external then -- if stream is being already loaded - just wait until the stream loading is finished if stream.loading then return ; end -- try to sychronize the data of the source and the external streams: local candle, from, to; -- get the oldest candle of the source candle = core.getcandle(stream.barSize, source:date(source_first), day_offset, week_offset); if stream.data == nil then from = getFromToLoad(candle, stream.length, stream.extent); stream.requestedFrom = from; stream.dataFrom = candle; stream.loading = true; if (source:isAlive()) then to = 0; else t, to = core.getcandle(stream.barSize, source:date(source:size() - 1), day_offset, week_offset); end stream.data = host:execute("getHistory", id, source:instrument(), stream.barSize, from, to, source:isBid()); elseif candle < stream.dataFrom then from = getFromToLoad(candle, stream.length, stream.extent); stream.requestedFrom = from; stream.dataFrom = candle; stream.loading = true; host:execute("extendHistory", id, stream.data, from, stream.data:date(0)); end end -- if the indicator is not created yet, creat id if stream.indicator == nil and stream.data ~= nil then indi_src = stream.data if isTick() then indi_src = indi_src[instance.parameters.Price] end stream.indicator = createIndicator(id, indi_src); end end -- the function is called when the external data is being loaded function registerDataLoaded(id) local stream = streams[id]; assert(stream ~= nil, "Stream is not registered"); if stream.external then stream.loading = false; stream.fullUpdate = true; end -- if all the streams has been loaded - update the indicator instance:updateFrom(source:size() - 1); 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 -- get date/time of the oldest candle which shall be requested -- @param candle - the oldest candle of the chosen time frame -- @param length - length of the bar in seconds -- @param extent - the required extent in bars for history data function getFromToLoad(candle, length, extent) local loadFrom; local nontrading, nontradingend; -- calculate the date and time of the candle "extent" bar prior to the oldest requested candle loadFrom = math.floor(candle * 86400 - length * extent + 0.5) / 86400; -- check whether the candle found is inside the non-trading period nontrading, nontradingend = core.isnontrading(loadFrom, day_offset); if nontrading then -- if it is non-trading, shift for two days to skip the non-trading periods loadFrom = math.floor((loadFrom - 2) * 86400 - length * extent + 0.5) / 86400; end return loadFrom; end